If you haven't installed the SDK yet, please head over to the QuickStart guide to get our SDK up and running in Eclipse. You can also check out our API Reference for more detailed information about our SDK.
The Parse platform provides a complete backend solution for your mobile application. Our goal is to totally eliminate the need for writing server code or maintaining servers.
If you're familiar with web frameworks like Ruby on Rails, we've taken many of the same principles and applied them to our platform. In particular, our SDK is ready to use out of the box with minimal configuration on your part.
On Parse, you create an App for each of your mobile applications. Each App has its own application id and client key that you apply to your SDK install. Your account on Parse can accommodate multiple Apps. This is useful even if you have one application, since you can deploy different versions for test and production.
Storing data on Parse is built around the Entity
. Each Entity
contains key-value pairs of JSON-compatible data. This data is schemaless, which means that you don't need to specify ahead of time what keys exist on each Entity
. You simply set whatever key-value pairs you want, and our backend will store it.
For example, let's say you're tracking high scores for a game. A single Entity
could contain:
score: 1337, playerName: "Sean Plott", cheatMode: false
Keys must be alphanumeric strings. Values can be strings, numbers, booleans, or even arrays and objects - anything that can be JSON-encoded.
Each Entity
has a class name that you can use to distinguish different sorts of data. For example, we could call the high score object a GameScore
. We recommend that you NameYourClassesLikeThis and nameYourKeysLikeThis, just to keep your code looking pretty.
Let's say you want to save the GameScore
described above to the Parse Cloud. The interface is similar to a Map
, plus the saveInBackground
method:
Entity gameScore = new Entity("GameScore"); gameScore.put("score", 1337); gameScore.put("playerName", "Sean Plott"); gameScore.put("cheatMode", false); gameScore.saveInBackground();
After this code runs, you will probably be wondering if anything really happened. To make sure the data was saved, you can look at the Data Browser in your app on Parse. You should see something like this:
objectId: "xWMyZ4YEGZ", score: 1337, playerName: "Sean Plott", cheatMode: false, createdAt:"2011-06-10T18:33:42Z", updatedAt:"2011-06-10T18:33:42Z"
There are two things to note here. You didn't have to configure or set up a new Class called GameScore
before running this code. Your Parse app lazily creates this Class for you when it first encounters it.
There are also a few fields you don't need to specify that are provided as a convenience. objectId
is a unique identifier for each saved object. createdAt
and updatedAt
represent the time that each object was created and last modified in the cloud. Each of these fields is filled in by Parse, so they don't exist on a Entity
until a save operation has completed.
Saving data to the cloud is fun, but it's even more fun to get that data out again. If you have the objectId
, you can retrieve the whole Entity
using a ParseQuery
:
ParseQuery<Entity> query = ParseQuery.getQuery("GameScore"); query.getInBackground("xWMyZ4YEGZ", new GetCallback<Entity>() { public void done(Entity object, ParseException e) { if (e == null) { // object will be your game score } else { // something went wrong } } });
To get the values out of the Entity
, there's a getX
method for each data type:
int score = gameScore.getInt("score"); String playerName = gameScore.getString("playerName"); boolean cheatMode = gameScore.getBoolean("cheatMode");
If you don't know what type of data you're getting out, you can call get(key)
, but then you probably have to cast it right away anyways. In most situations you should use the typed accessors like getString
.
The three special values have their own accessors:
String objectId = gameScore.getObjectId(); Date updatedAt = gameScore.getUpdatedAt(); Date createdAt = gameScore.getCreatedAt();
If you need to refresh an object you already have with the latest data that
is in the cloud, you can call the fetchInBackground
method like so:
myObject.fetchInBackground(new GetCallback<Entity>() { public void done(Entity object, ParseException e) { if (e == null) { // Success! } else { // Failure! } } });
The code in the GetCallback
will be run on the main thread.
Parse also lets you store objects in a local datastore on the Android device itself. You can use this for data that doesn't need to be saved to the cloud, but this is especially useful for temporarily storing data so that it can be synced later. To enable the datastore, call Parse.enableLocalDatastore()
in your Application
constructor before calling Parse.initialize()
. Once the local datastore is enabled, you can store an object by pinning it.
Entity gameScore = new Entity("GameScore"); gameScore.put("score", 1337); gameScore.put("playerName", "Sean Plott"); gameScore.put("cheatMode", false); gameScore.pinInBackground();
As with saving, this recursively stores every object and file that gameScore
points to, if it has been fetched from the cloud. Whenever you save changes to the object, or fetch new changes from Parse, the copy in the datastore will be automatically updated, so you don't have to worry about it.
Storing an object is only useful if you can get it back out. To get the data for a specific object, you can use a ParseQuery
just like you would while on the network, but using the fromLocalDatastore
method to tell it where to get the data.
ParseQuery<Entity> query = ParseQuery.getQuery("GameScore"); query.fromLocalDatastore(); query.getInBackground("xWMyZ4YEGZ", new GetCallback<Entity>() { public void done(Entity object, ParseException e) { if (e == null) { // object will be your game score } else { // something went wrong } } });
If you already have an instance of the object, you can instead use the fetchFromLocalDatastoreInBackground
method.
Entity object = Entity.createWithoutData("GameScore", "xWMyZ4YEGZ"); object.fetchFromLocalDatastoreInBackground(new GetCallback<Entity>() { public void done(Entity object, ParseException e) { if (e == null) { // object will be your game score } else { // something went wrong } } });
When you are done with the object and no longer need to keep it on the device, you can release it with unpinInBackground
.
gameScore.unpinInBackground();
Most save functions execute immediately, and inform your app when the save is complete. If you don't need to know when the save has finished, you can use saveEventually
instead. The advantage is that if the user currently doesn't have a network connection, saveEventually
will store the update on the device until a network connection is re-established. If your app is closed before the connection is back, Parse will try again the next time the app is opened. All calls to saveEventually
(and deleteEventually
) are executed in the order they are called, so it is safe to call saveEventually
on an object multiple times. If you have the local datastore enabled, then any object you saveEventually
will be pinned as long as that save is in progress. That makes it easy to retrieve your local changes while waiting for the network to be available.
Entity gameScore = new Entity("GameScore"); gameScore.put("score", 1337); gameScore.put("playerName", "Sean Plott"); gameScore.put("cheatMode", false); gameScore.saveEventually();
Updating an object is simple. Just set some new data on it and call one of the save methods. Assuming you have saved the object and have the objectId
, you can retrieve the Entity
using a ParseQuery
and update its data:
ParseQuery<Entity> query = ParseQuery.getQuery("GameScore"); // Retrieve the object by id query.getInBackground("xWMyZ4YEGZ", new GetCallback<Entity>() { public void done(Entity gameScore, ParseException e) { if (e == null) { // Now let's update it with some new data. In this case, only cheatMode and score // will get sent to the Parse Cloud. playerName hasn't changed. gameScore.put("score", 1338); gameScore.put("cheatMode", true); gameScore.saveInBackground(); } } });
Parse automatically figures out which data has changed so only "dirty" fields will be transmitted during a save. You don't need to worry about squashing data in the cloud that you didn't intend to update.
The above example contains a common use case. The "score" field is a counter that we'll need to continually update with the player's latest score. Using the above method works but it's cumbersome and can lead to problems if you have multiple clients trying to update the same counter.
To help with storing counter-type data, Parse provides methods that atomically increment (or decrement) any number field. So, the same update can be rewritten as:
gameScore.increment("score"); gameScore.saveInBackground();
You can also increment by any amount using increment(key, amount)
.
To help with storing array data, there are three operations that can be used to atomically change an array field:
add
and addAll
append the given objects to the end of an array field.addUnique
and addAllUnique
add only the given objects which aren't already contained in an array field to that field. The position of the insert is not guaranteed.removeAll
removes all instances of the given objects from an array field.For example, we can add items to the set-like "skills" field like so:
gameScore.addAllUnique("skills", Arrays.asList("flying", "kungfu")); gameScore.saveInBackground();
Note that it is not currently possible to atomically add and remove items from an array in the same save.
You will have to call save
in between every different kind of array operation.
To delete an object from the Parse Cloud:
myObject.deleteInBackground();
If you want to run a callback when the delete is confirmed, you can provide a DeleteCallback
to the deleteInBackground
method. If you want to block the calling thread, you can use the delete
method.
You can delete a single field from an object with the remove
method:
// After this, the playerName field will be empty myObject.remove("playerName"); // Saves the field deletion to the Parse Cloud myObject.saveInBackground();
Objects can have relationships with other objects. To model this behavior, any Entity
can be used as a value in other Entity
s. Internally, the Parse framework will store the referred-to object in just one place, to maintain consistency.
For example, each Comment
in a blogging app might correspond to one Post
. To create a new Post
with a single Comment
, you could write:
// Create the post Entity myPost = new Entity("Post"); myPost.put("title", "I'm Hungry"); myPost.put("content", "Where should we go for lunch?"); // Create the comment Entity myComment = new Entity("Comment"); myComment.put("content", "Let's do Sushirrito."); // Add a relation between the Post and Comment myComment.put("parent", myPost); // This will save both myPost and myComment myComment.saveInBackground();
You can also link objects using just their objectId
s like so:
// Add a relation between the Post with objectId "1zEcyElZ80" and the comment myComment.put("parent", Entity.createWithoutData("Post", "1zEcyElZ80"));
By default, when fetching an object, related Entity
s are not fetched. These objects' values cannot be retrieved until they have been fetched like so:
fetchedComment.getParseObject("post") .fetchIfNeededInBackground(new GetCallback<Entity>() { public void done(Entity post, ParseException e) { String title = post.getString("title"); // Do something with your new title variable } });
You can also model a many-to-many relation using the ParseRelation
object. This works similar to List
, except that you don't need to download all the Entity
s in a relation at once. This allows ParseRelation
to scale to many more objects than the List
approach. For example, a User
may have many Post
s that they might like. In this case, you can store the set of Post
s that a User
likes using getRelation
. In order to add a post to the list, the code would look something like:
ParseUser user = ParseUser.getCurrentUser(); ParseRelation<Entity> relation = user.getRelation("likes"); relation.add(post); user.saveInBackground();
You can remove a post from the ParseRelation
with something like:
relation.remove(post);
By default, the list of objects in this relation are not downloaded. You can get the list of Post
s by calling findInBackground
on the ParseQuery
returned by getQuery
. The code would look like:
relation.getQuery().findInBackground(new FindCallback<Entity>() { void done(List<Entity> results, ParseException e) { if (e != null) { // There was an error } else { // results have all the Posts the current user liked. } } });
If you want only a subset of the Post
s you can add extra constraints to the ParseQuery
returned by getQuery
. The code would look something like:
ParseQuery<Entity> query = relation.getQuery(); // Add other query constraints.
For more details on ParseQuery
, please look at the query portion of this guide. A ParseRelation
behaves similar to a List
for querying purposes, so any queries you can do on lists of objects (other than include
) you can do on ParseRelation
.
So far we've used values with type String
, int
, bool
, and Entity
. Parse also supports java.util.Date
, byte[]
, and JSONObject.NULL
.
You can nest JSONObject
and JSONArray
objects to store more structured data within a single Entity
.
Some examples:
int myNumber = 42; String myString = "the number is " + myNumber; Date myDate = new Date(); JSONArray myArray = new JSONArray(); myArray.put(myString); myArray.put(myNumber); JSONObject myObject = new JSONObject(); myObject.put("number", myNumber); myObject.put("string", myString); byte[] myData = { 4, 8, 16, 32 }; Entity bigObject = new Entity("BigObject"); bigObject.put("myNumber", myNumber); bigObject.put("myString", myString); bigObject.put("myDate", myDate); bigObject.put("myData", myData); bigObject.put("myArray", myArray); bigObject.put("myObject", myObject); bigObject.put("myNull", JSONObject.NULL); bigObject.saveInBackground();
We do not recommend storing large pieces of binary data like images or documents using byte[]
fields on Entity
. ParseObjects
s should not exceed 128 kilobytes in size. To store more, we recommend you use ParseFile
. See the guide section for more details.
For more information about how Parse handles data, check out our documentation on Data & Security.
In many cases, getInBackground
isn't powerful enough to specify which objects you want to retrieve. The ParseQuery
offers different ways to retrieve a list of objects rather than just a single object.
The general pattern is to create a ParseQuery
, put conditions on it, and then retrieve a List
of matching Entity
s using the findInBackground
method with a FindCallback
. For example, to retrieve scores with a particular playerName
, use the whereEqualTo
method to constrain the value for a key:
ParseQuery<Entity> query = ParseQuery.getQuery("GameScore"); query.whereEqualTo("playerName", "Dan Stemkoski"); query.findInBackground(new FindCallback<Entity>() { public void done(List<Entity> scoreList, ParseException e) { if (e == null) { Log.d("score", "Retrieved " + scoreList.size() + " scores"); } else { Log.d("score", "Error: " + e.getMessage()); } } });
findInBackground
works similarly to getInBackground
in that it assures the network request is done on a background thread, and runs its callback in the main thread.
There are several ways to put constraints on the objects found by a ParseQuery
. You can filter out objects with a particular key-value pair with whereNotEqualTo
:
query.whereNotEqualTo("playerName", "Michael Yabuti");
You can give multiple constraints, and objects will only be in the results if they match all of the constraints. In other words, it's like an AND of constraints.
query.whereNotEqualTo("playerName", "Michael Yabuti"); query.whereGreaterThan("playerAge", 18);
You can limit the number of results with setLimit
. By default, results are limited to 100, but anything from 1 to 1000 is a valid limit:
query.setLimit(10); // limit to at most 10 results
If you want exactly one result, a more convenient alternative may be to use getFirst
or getFirstBackground
instead of using find
.
ParseQuery<Entity> query = ParseQuery.getQuery("GameScore"); query.whereEqualTo("playerEmail", "dstemkoski@example.com"); query.getFirstInBackground(new GetCallback<Entity>() { public void done(Entity object, ParseException e) { if (object == null) { Log.d("score", "The getFirst request failed."); } else { Log.d("score", "Retrieved the object."); } } });
You can skip the first results with setSkip
. This can be useful for pagination:
query.setSkip(10); // skip the first 10 results
For sortable types like numbers and strings, you can control the order in which results are returned:
// Sorts the results in ascending order by the score field query.orderByAscending("score"); // Sorts the results in descending order by the score field query.orderByDescending("score");
You can add more sort keys to the query as follows:
// Sorts the results in ascending order by the score field if the previous sort keys are equal. query.addAscendingOrder("score"); // Sorts the results in descending order by the score field if the previous sort keys are equal. query.addDescendingOrder("score");
For sortable types, you can also use comparisons in queries:
// Restricts to wins < 50 query.whereLessThan("wins", 50); // Restricts to wins <= 50 query.whereLessThanOrEqualTo("wins", 50); // Restricts to wins > 50 query.whereGreaterThan("wins", 50); // Restricts to wins >= 50 query.whereGreaterThanOrEqualTo("wins", 50);
If you want to retrieve objects matching several different values, you can use whereContainedIn
, providing a collection of acceptable values. This is often useful to replace multiple queries with a single query. For example, if you want to retrieve scores made by any player in a particular list:
String[] names = {"Jonathan Walsh", "Dario Wunsch", "Shawn Simon"}; query.whereContainedIn("playerName", Arrays.asList(names));
If you want to retrieve objects that do not match any of several values you can use whereKey:notContainedIn:
, providing an array of acceptable values. For example, if you want to retrieve scores from players besides those in a list:
String[] names = {"Jonathan Walsh", "Dario Wunsch", "Shawn Simon"}; query.whereNotContainedIn("playerName", Arrays.asList(names));
If you want to retrieve objects that have a particular key set, you can use whereExists
. Conversely, if you want to retrieve objects without a particular key set, you can use whereDoesNotExist
.
// Finds objects that have the score set query.whereExists("score"); // Finds objects that don't have the score set query.whereDoesNotExist("score");
You can use the whereMatchesKeyInQuery
method to get objects where a key matches the value of a key in a set of objects resulting from another query. For example, if you have a class containing sports teams and you store a user's hometown in the user class, you can issue one query to find the list of users whose hometown teams have winning records. The query would look like:
ParseQuery<Entity> teamQuery = ParseQuery.getQuery("Team"); teamQuery.whereGreaterThan("winPct", 0.5); ParseQuery<ParseUser> userQuery = ParseUser.getQuery(); userQuery.whereMatchesKeyInQuery("hometown", "city", teamQuery); userQuery.findInBackground(new FindCallback<ParseUser>() { void done(List<ParseUser> results, ParseException e) { // results has the list of users with a hometown team with a winning record } });
Conversely, to get objects where a key does not match the value of a key in a set of objects resulting from another query, use whereDoesNotMatchKeyInQuery
. For example, to find users whose hometown teams have losing records:
ParseQuery<ParseUser> losingUserQuery = ParseUser.getQuery(); losingUserQuery.whereDoesNotMatchKeyInQuery("hometown", "city", teamQuery); losingUserQuery.findInBackground(new FindCallback<ParseUser>() { void done(List<ParseUser> results, ParseException e) { // results has the list of users with a hometown team with a losing record } });
You can restrict the fields returned by calling selectKeys
with a collection of keys. To retrieve documents that contain only the score
and playerName
fields (and also special built-in fields such as objectId
, createdAt
, and updatedAt
):
ParseQuery<Entity> query = ParseQuery.getQuery("GameScore"); query.selectKeys(Arrays.asList("playerName", "score"));; List<Entity> results = query.find();
The remaining fields can be fetched later by calling one of the fetchIfNeeded
variants on the returned objects:
Entity object = results.get(0); object.fetchIfNeededInBackground(new GetCallback<Entity>() { public void done(Entity object, ParseException e) { // all fields of the object will now be available here. } });
If a key contains an array value, you can search for objects where the key's array value contains 2 by:
// Find objects where the array in arrayKey contains the number 2. query.whereEqualTo("arrayKey", 2);
You can also search for objects where the key's array value contains each of the values 2, 3, and 4 with the following:
// Find objects where the array in arrayKey contains all of the numbers 2, 3, and 4. ArrayList<Integer> numbers = new ArrayList<Integer>(); numbers.add(2); numbers.add(3); numbers.add(4); query.whereContainsAll("arrayKey", numbers);
Use whereStartsWith
to restrict to string values that start with a particular string. Similar to a MySQL LIKE operator, this is indexed so it is efficient for large datasets:
// Finds barbecue sauces that start with "Big Daddy's". ParseQuery<Entity> query = ParseQuery.getQuery("BarbecueSauce"); query.whereStartsWith("name", "Big Daddy's");
There are several ways to issue queries for relational data. If you want to retrieve objects where a field matches a particular Entity
, you can use whereEqualTo
just like for other data types. For example, if each Comment
has a Post
object in its post
field, you can fetch comments for a particular Post
:
// Assume Entity myPost was previously created. ParseQuery<Entity> query = ParseQuery.getQuery("Comment"); query.whereEqualTo("post", myPost); query.findInBackground(new FindCallback<Entity>() { public void done(List<Entity> commentList, ParseException e) { // commentList now has the comments for myPost } });
If you want to retrieve objects where a field contains a Entity
that matches a different query, you can use whereMatchesQuery
. Note that the default limit of 100 and maximum limit of 1000 apply to the inner query as well, so with large data sets you may need to construct queries carefully to get the desired behavior. In order to find comments for posts containing images, you can do:
ParseQuery<Entity> innerQuery = ParseQuery.getQuery("Post"); innerQuery.whereExists("image"); ParseQuery<Entity> query = ParseQuery.getQuery("Comment"); query.whereMatchesQuery("post", innerQuery); query.findInBackground(new FindCallback<Entity>() { public void done(List<Entity> commentList, ParseException e) { // comments now contains the comments for posts with images. } });
If you want to retrieve objects where a field contains a Entity
that does not match a different query, you can use whereDoesNotMatchQuery
. In order to find comments for posts without images, you can do:
ParseQuery<Entity> innerQuery = ParseQuery.getQuery("Post"); innerQuery.whereExists("image"); ParseQuery<Entity> query = ParseQuery.getQuery("Comment"); query.whereDoesNotMatchQuery("post", innerQuery); query.findInBackground(new FindCallback<Entity>() { public void done(List<Entity> commentList, ParseException e) { // comments now contains the comments for posts without images. } });
In some situations, you want to return multiple types of related objects in one query. You can do this with the include
method. For example, let's say you are retrieving the last ten comments, and you want to retrieve their related posts at the same time:
ParseQuery<Entity> query = ParseQuery.getQuery("Comment"); // Retrieve the most recent ones query.orderByDescending("createdAt"); // Only retrieve the last ten query.setLimit(10); // Include the post data with each comment query.include("post"); query.findInBackground(new FindCallback<Entity>() { public void done(List<Entity> commentList, ParseException e) { // commentList now contains the last ten comments, and the "post" // field has been populated. For example: for (Entity comment : commentList) { // This does not require a network access. Entity post = comment.getParseObject("post"); Log.d("post", "retrieved a related post"); } } });
You can also do multi level includes using dot notation. If you wanted to include the post for a comment and the post's author as well you can do:
query.include("post.author");
You can issue a query with multiple fields included by calling include
multiple times. This functionality also works with ParseQuery helpers like getFirst()
and getInBackground()
.
If you have enabled the local datastore by calling Parse.enableLocalDatastore()
before your call to Parse.initialize()
, then you can also query against the objects stored locally on the device. To do this, call the fromLocalDatastore
method on the query.
query.fromLocalDatastore(); query.findInBackground(new FindCallback<Entity>() { public void done(List<Entity> scoreList, ParseException e) { if (e == null) { // Results were successfully found from the local datastore. } else { // There was an error. } } });
You can query from the local datastore using exactly the same kinds of queries you use over the network. The results will include every object that matches the query that's been pinned to your device. The query even takes into account any changes you've made to the object that haven't yet been saved to the cloud. For example, if you call deleteEventually
, on an object, it will no longer be returned from these queries.
It's often useful to cache the result of a query on a device. This lets you show data when the user's device is offline, or when the app has just started and network requests have not yet had time to complete. The easiest way to do this is with the local datastore. When you pin objects, you can attach a label to the pin, which lets you manage a group of objects together. For example, to cache the results of the query above, you can call pinAllInBackground
and give it a label.
final String TOP_SCORES_LABEL = "topScores"; // Query for the latest objects from Parse. query.findInBackground(new FindCallback<Entity>() { public void done(final List<Entity> scoreList, ParseException e) { if (e != null) { // There was an error or the network wasn't available. return; } // Release any objects previously pinned for this query. Entity.unpinAllInBackground(TOP_SCORES_LABEL, scoreList, new DeleteCallback() { public void done(ParseException e) { if (e != null) { // There was some error. return; } // Add the latest results for this query to the cache. Entity.pinAllInBackground(TOP_SCORES_LABEL, scoreList); } }); } });
Now when you do any query with fromLocalDatastore
, these objects will be included in the results if they still match the query.
If you aren't using the local datastore, you can use the per-query cache for ParseQuery
instead. The default query behavior doesn't use the cache, but you can enable caching with setCachePolicy
. For example, to try the network and then fall back to cached data if the network is not available:
query.setCachePolicy(ParseQuery.CachePolicy.NETWORK_ELSE_CACHE); query.findInBackground(new FindCallback<Entity>() { public void done(List<Entity> scoreList, ParseException e) { if (e == null) { // Results were successfully found, looking first on the // network and then on disk. } else { // The network was inaccessible and we have no cached data // for this query. } } });
Parse provides several different cache policies:
IGNORE_CACHE
IGNORE_CACHE
is the default cache policy.
CACHE_ONLY
ParseException
.
NETWORK_ONLY
CACHE_ELSE_NETWORK
ParseException
.
NETWORK_ELSE_CACHE
ParseException
.
CACHE_THEN_NETWORK
FindCallback
will actually be called twice - first with the cached results, then with the network results. This cache policy can only be used asynchronously with findInBackground
.
If you need to control the cache's behavior, you can use methods provided in ParseQuery to interact with the cache. You can do the following operations on the cache:
boolean isInCache = query.hasCachedResult();
query.clearCachedResult();
ParseQuery.clearAllCachedResults();
query.setMaxCacheAge(TimeUnit.DAYS.toMillis(1));
Query caching also works with ParseQuery helpers including getFirst()
and getInBackground()
.
If you just need to count how many objects match a query, but you do not need to retrieve all the objects that match, you can use count
instead of find
. For example, to count how many games have been played by a particular player:
ParseQuery<Entity> query = ParseQuery.getQuery("GameScore"); query.whereEqualTo("playerName", "Sean Plott"); query.countInBackground(new CountCallback() { public void done(int count, ParseException e) { if (e == null) { // The count request succeeded. Log the count Log.d("score", "Sean has played " + count + " games"); } else { // The request failed } } });
If you want to block the calling thread, you can also use the synchronous query.count()
method.
For classes with over 1000 objects, count operations are limited by timeouts. They may routinely yield timeout errors or return results that are only approximately correct. Thus, it is preferable to architect your application to avoid this sort of count operation.
If you want to find objects that match one of several queries, you can use ParseQuery.or
method to construct a query that is an or of the queries passed in. For instance if you want to find players who either have a lot of wins or a few wins, you can do:
ParseQuery<Entity> lotsOfWins = ParseQuery.getQuery("Player"); lotsOfWins.whereGreaterThan(150); ParseQuery<Entity> fewWins = ParseQuery.getQuery("Player"); fewWins.whereLessThan(5); List<ParseQuery<Entity>> queries = new ArrayList<ParseQuery<Entity>>(); queries.add(lotsOfWins); queries.add(fewWins); ParseQuery<Entity> mainQuery = ParseQuery.or(queries); mainQuery.findInBackground(new FindCallback<Entity>() { public void done(List<Entity> results, ParseException e) { // results has the list of players that win a lot or haven't won much. } });
You can add additional constraints to the newly created ParseQuery
that act as an 'and' operator.
Note that we do not, however, support non-filtering constraints
(e.g. setLimit
, skip
, orderBy...
, include
)
in the subqueries of the compound query.
Parse is designed to get you up and running as quickly as possible. You can access all of your data using the Entity
class and access any field with get()
. In mature codebases, subclasses have many advantages, including terseness, extensibility, and support for autocomplete. Subclassing is completely optional, but can transform this code:
Entity shield = new Entity("Armor"); shield.put("displayName", "Wooden Shield"); shield.put("fireproof", false); shield.put("rupees", 50);
Into this:
Armor shield = new Armor(); shield.setDisplayName("Wooden Shield"); shield.setFireproof(false); shield.setRupees(50);
To create a Entity
subclass:
Entity
.@ParseClassName
annotation. Its value should be the string you would pass into the Entity
constructor, and makes all future class name references unnecessary.Entity
fields in this constructor.Entity.registerSubclass(YourClass.class)
in your Application
constructor before calling Parse.initialize()
.Armor
subclass of Entity
:
// Armor.java import com.parse.Entity; import com.parse.ParseClassName; @ParseClassName("Armor") public class Armor extends Entity { } // App.java import com.parse.Parse; import android.app.Application; public class App extends Application { @Override public void onCreate() { super.onCreate(); Entity.registerSubclass(Armor.class); Parse.initialize(this, PARSE_APPLICATION_ID, PARSE_CLIENT_KEY); } }
Adding methods to your Entity
subclass helps encapsulate logic about the class. You can keep all your logic about a subject in one place rather than using separate classes for business logic and storage/transmission logic.
You can add accessors and mutators for the fields of your Entity
easily. Declare the getter and setter for the field as you normally would, but implement them in terms of get()
and put()
. The following example creates a displayName
field in the Armor
class:
// Armor.java @ParseClassName("Armor") public class Armor extends Entity { public String getDisplayName() { return getString("displayName"); } public void setDisplayName(String value) { put("displayName", value); } }
You can now access the displayName field using armor.getDisplayName()
and assign to it using armor.setDisplayName("Wooden Sword")
. This allows your IDE to provide autocompletion as you develop your app and allows typos to be caught at compile-time.
Accessors and mutators of various types can be easily defined in this manner using the various forms of get()
such as getInt()
, getParseFile()
, or getMap()
.
If you need more complicated logic than simple field access, you can declare your own methods as well:
public void takeDamage(int amount) { // Decrease the armor's durability and determine whether it has broken increment("durability", -amount); if (getDurability() < 0) { setBroken(true); } }
You should create new instances of your subclasses using the constructors you have defined. Your subclass must define a public default constructor that does not modify fields of the Entity
, which will be used throughout the Parse SDK to create strongly-typed instances of your subclass.
To create a reference to an existing object, use Entity.createWithoutData()
:
Armor armorReference = Entity.createWithoutData(Armor.class, armor.getObjectId());
You can get a query for objects of a particular subclass using the static method ParseQuery.getQuery()
. The following example queries for armors that the user can afford:
ParseQuery<Armor> query = ParseQuery.getQuery(Armor.class); query.whereLessThanOrEqualTo("rupees", ParseUser.getCurrentUser().get("rupees")); query.findInBackground(new FindCallback<Armor>() { @Override public void done(List<Armor> results, ParseException e) { for (Armor a : results) { // ... } } });
ParseFile
lets you store application files in the cloud that would otherwise be too large or cumbersome to fit into a regular Entity
. The most common use case is storing images but you can also use it for documents, videos, music, and any other binary data (up to 10 megabytes).
Getting started with ParseFile
is easy. First, you'll need to have the data in byte[]
form and then create a ParseFile
with it. In this example, we'll just use a string:
byte[] data = "Working at Parse is great!".getBytes(); ParseFile file = new ParseFile("resume.txt", data);
Notice in this example that we give the file a name of resume.txt
. There's two things to note here:
resume.txt
..png
.Next you'll want to save the file up to the cloud. As with Entity
, there are many variants of the save
method you can use depending on what sort of callback and error handling suits you.
file.saveInBackground();
Finally, after the save completes, you can associate a ParseFile
onto a Entity
just like any other piece of data:
Entity jobApplication = new Entity("JobApplication"); jobApplication.put("applicantName", "Joe Smith"); jobApplication.put("applicantResumeFile", file); jobApplication.saveInBackground();
Retrieving it back involves calling one of the getData
variants on the Entity
. Here we retrieve the resume file off another JobApplication object:
ParseFile applicantResume = (ParseFile)anotherApplication.get("applicantResumeFile"); applicantResume.getDataInBackground(new GetDataCallback() { public void done(byte[] data, ParseException e) { if (e == null) { // data has the bytes for the resume } else { // something went wrong } } });
Just like on Entity
, you will most likely want to use the background version of getData
.
It's easy to get the progress of both uploads and downloads using ParseFile by passing a ProgressCallback to saveInBackground
and getDataInBackground
. For example:
byte[] data = "Working at Parse is great!".getBytes(); ParseFile file = new ParseFile("resume.txt", data); file.saveInBackground(new SaveCallback() { public void done(ParseException e) { // Handle success or failure here ... } }, new ProgressCallback() { public void done(Integer percentDone) { // Update your progress spinner here. percentDone will be between 0 and 100. } });
You can delete files that are referenced by objects using the REST API. You will need to provide the master key in order to be allowed to delete a file.
If your files are not referenced by any object in your app, it is not possible to delete them through the REST API. You may request a cleanup of unused files in your app's Settings page. Keep in mind that doing so may break functionality which depended on accessing unreferenced files through their URL property. Files that are currently associated with an object will not be affected.
Parse provides a number of hooks for you to get a glimpse into the ticking heart of your app. We understand that it's important to understand what your app is doing, how frequently, and when.
While this section will cover different ways to instrument your app to best take advantage of Parse's analytics backend, developers using Parse to store and retrieve data can already take advantage of metrics on Parse.
Without having to implement any client-side logic, you can view real-time graphs and breakdowns (by device type, Parse class name, or REST verb) of your API Requests in your app's dashboard and save these graph filters to quickly access just the data you're interested in.
Our initial analytics hook allows you to track your application being launched.
By adding the following line to the onCreate
method of your main Activity
,
you'll begin to collect data on when and how often your application is opened.
ParseAnalytics.trackAppOpened(getIntent());
Graphs and breakdowns of your statistics are accessible from your app's Dashboard.
Further analytics are available around push notification delivery and open rates. Take a look at the Tracking Pushes and App Opens subsection of our Push Guide for more detailed information on handling notification payloads and push-related callbacks.
ParseAnalytics
also allows you to track free-form events,
with a handful of String
keys and values.
These extra dimensions allow segmentation of your custom events
via your app's Dashboard.
Say your app offers search functionality for apartment listings, and you want to track how often the feature is used, with some additional metadata.
Map<String, String> dimensions = new HashMap<String, String>(); // Define ranges to bucket data points into meaningful segments dimensions.put("priceRange", "1000-1500"); // Did the user filter the query? dimensions.put("source", "craigslist"); // Do searches happen more often on weekdays or weekends? dimensions.put("dayType", "weekday"); // Send the dimensions to Parse along with the 'search' event ParseAnalytics.trackEvent("search", dimensions);
ParseAnalytics
can even be used as a lightweight error tracker —
simply invoke the following and you'll have access to
an overview of the rate and frequency of errors,
broken down by error code, in your application:
Map<String, String> dimensions = new HashMap<String, String>(); dimensions.put('code', Integer.toString(error.getCode())); ParseAnalytics.trackEvent('error', dimensions);
Note that Parse currently only stores the first eight dimension pairs
per call to ParseAnalytics.trackEvent()
.
ParseConfig
is a way to configure your applications remotely by storing a single configuration object on Parse.
It enables you to add things like feature gating or a simple "Message of the Day".
To start using ParseConfig
you need to add a few key/value pairs (parameters) to your app on the Parse Config Dashboard.
After that you will be able to fetch the ParseConfig
on the client, like in this example:
ParseConfig.getInBackground(new ConfigCallback() { @Override public void done(ParseConfig config, ParseException e) { int number = config.getInt("winningNumber"); Log.d("TAG", String.format("Yay! The number is %d!", number)); } });
ParseConfig
is built to be as robust and reliable as possible, even in the face of poor internet connections.
Caching is used by default to ensure that the latest successfully fetched config is always available.
In the below example we use getInBackground
to retrieve the latest version of config from the server,
and if the fetch fails we can simply fall back to the version that we successfully fetched before via getCurrentConfig
.
Log.d("TAG", "Getting the latest config..."); ParseConfig.getInBackground(new ConfigCallback() { @Override public void done(ParseConfig config, ParseException e) { if (e == null) { Log.d("TAG", "Yay! Config was fetched from the server."); } else { Log.e("TAG", "Failed to fetch. Using Cached Config."); config = ParseConfig.getCurrentConfig(); } // Get the message from config or fallback to default value String welcomeMessage = config.getString("welcomeMessage", "Welcome!"); Log.d("TAG", String.format("Welcome Messsage From Config = %s", welcomeMessage)); } });
Every ParseConfig
instance that you get is always immutable.
When you retrieve a new ParseConfig
in the future from the network,
it will not modify any existing ParseConfig
instance,
but will instead create a new one and make it available via ParseConfig.getCurrentConfig()
.
Therefore, you can safely pass around any ParseConfig
object and safely assume that it will not automatically change.
It might be troublesome to retrieve the config from the server every time you want to use it.
You can avoid this by simply using the cached getCurrentConfig
object and fetching the config only once in a while.
class Helper { private static final long configRefreshInterval = 12 * 60 * 60 * 1000; private static long lastFetchedTime; // Fetches the config at most once every 12 hours per app runtime public static void refreshConfig() { long currentTime = System.currentTimeMillis(); if (currentTime - lastFetchedTime > configRefreshInterval) { lastFetchedTime = currentTime; ParseConfig.getInBackground(); } } }
ParseConfig
supports most of the data types supported by Entity
:
We currently allow up to 100 parameters in your config and a total size of 128KB across all parameters.
The Parse Android SDK provides a local datastore which can be used to store and retrieve Entity
s, even when the network is unavailable. To enable this functionality, simply call Parse.enableLocalDatastore()
before your call to initialize
.
import com.parse.Parse; import android.app.Application; public class App extends Application { @Override public void onCreate() { super.onCreate(); Parse.enableLocalDatastore(this); Parse.initialize(this, PARSE_APPLICATION_ID, PARSE_CLIENT_KEY); } }
There are a couple of side effects of enabling the local datastore that you should be aware of.
When enabled, there will only be one instance of any given Entity
. For example,
imagine you have an instance of the "GameScore"
class with an objectId
of
"xWMyZ4YEGZ"
, and then you issue a ParseQuery
for all instances of
"GameScore"
with that objectId
. The result will be the same instance
of the object you already have in memory.
Another side effect is that the current user and current installation will be stored in the local datastore, so you can persist unsaved changes to these objects between runs of your app using the methods below.
Calling the saveEventually
method on a Entity
will cause the
object to be pinned in the local datastore until the save completes. So now, if you change
the current ParseUser
and call ParseUser.getCurrent().saveEventually()
,
your app will always see the changes that you have made.
You can store a Entity
in the local datastore by pinning it. Pinning
a Entity
is recursive, just like saving, so any objects that are pointed to
by the one you are pinning will also be pinned. When an object is pinned, every time
you update it by fetching or saving new data, the copy in the local datastore will be updated
automatically. You don't need to worry about it at all.
Entity gameScore = new Entity("GameScore"); gameScore.put("score", 1337); gameScore.put("playerName", "Sean Plott"); gameScore.put("cheatMode", false); gameScore.pinInBackground();
If you have multiple objects, you can pin them all at once with the pinAllInBackground
convenience method.
Entity.pinAllInBackground(listOfObjects);
Storing objects is great, but it's only useful if you can then get the objects back out later.
Retrieving an object from the local datastore works just like retrieving one over the network.
The only difference is calling the fromLocalDatastore
method to tell the
ParseQuery
where to look for its results.
ParseQuery<Entity> query = ParseQuery.getQuery(“GameScore"); query.fromLocalDatastore(); query.getInBackground("xWMyZ4YE", new GetCallback<Entity>() { public void done(Entity object, ParseException e) { if (e == null) { // object will be your game score } else { // something went wrong } } });
Often, you'll want to find a whole list of objects that match certain criteria, instead of
getting a single object by id. To do that, you can use a
ParseQuery. Any ParseQuery
can be
used with the local datastore just as with the network. The results will include any object you
have pinned that matches the query. Any unsaved changes you have made to the object will be
considered when evaluating the query. So you can find a local object that matches, even if it
was never returned from the server for this particular query.
ParseQuery<Entity> query = ParseQuery.getQuery("GameScore"); query.whereEqualTo("playerName", "Joe Bob"); query.fromLocalDatastore(); query.findInBackground(new FindCallback<Entity>() { public void done(List<Entity> scoreList, ParseException e) { if (e == null) { Log.d("score", "Retrieved " + scoreList.size()); } else { Log.d("score", "Error: " + e.getMessage()); } } });
When you are done with an object and no longer need it to be in the local datastore, you can simply unpin it. This will free up disk space on the device and keep your queries on the local datastore running quickly.
gameScore.unpinInBackground();
There's also a method to unpin several objects at once.
Entity.unpinAllInBackground(listOfObjects);
Manually pinning and unpinning each object individual is a bit like using
malloc
and free
. It is a very powerful tool, but
it can be difficult to manage what objects get stored in complex scenarios.
For example, imagine you are making a game with separate high score lists for
global high scores and your friends' high scores. If one of your friends happens
to have a globally high score, you need to make sure you don't unpin them
completely when you remove them from one of the cached queries.
To make these scenarios easier, you can also pin with a label. Labels
indicate a group of objects that should be stored together.
// Add several objects with a label. Entity.pinAllInBackground("MyScores", someGameScores); // Add another object with the same label. anotherGameScore.pinInBackground("MyScores");
To unpin all of the objects with the same label at the same time, you can pass a label to the unpin methods. This saves you from having to manually track which objects are in each group you care about.
Entity.unpinAllInBackground("MyScores");
Any object will stay in the datastore as long as it is pinned with any label. In other words, if you pin an object with two different labels, and then unpin it with one label, the object will stay in the datastore until you also unpin it with the other label.
Pinning with labels makes it easy to cache the results of queries. You can use one label to pin the results of each different query. To get new results from the network, just do a query and update the pinned objects.
ParseQuery<Entity> query = ParseQuery.getQuery(“GameScore"); query.orderByDescending(“score”); // Query for new results from the network. query.findInBackground(new FindCallback<Entity>() { public void done(List<Entity> scores, ParseException e) { // Remove the previously cached results. Entity.unpinAllInBackground(“highScores”, new DeleteCallback() { public void done(ParseException e) { // Cache the new results. Entity.pinAllInBackground(“highScores”, scores); } }); } });
When you want to get the cached results for the query, you can then run the same query against the local datastore.
ParseQuery<Entity> query = ParseQuery.getQuery(“GameScore"); query.orderByDescending(“score”); query.fromLocalDatastore(); query.findInBackground(new FindCallback<Entity>() { public void done(List<Entity> scores, ParseException e) { // Yay! Cached scores! } });
Once you've saved some changes locally, there are a few different ways
you can save those changes back to Parse over the network. The easiest way
to do this is with saveEventually
. When you call saveEventually
on a Entity
, it will be pinned until it can be saved. The SDK will
make sure to save the object the next time the network is available.
gameScore.saveEventually();
If you'd like to have more control over the way objects are synced, you can
keep them in the local datastore until you are ready to save them yourself using
saveInBackground
. To manage the set of objects that need to be saved,
you can again use a label. The fromPin
method on ParseQuery
makes it easy to fetch just the objects you care about.
ParseQuery<Entity> query = ParseQuery.getQuery(“GameScore"); query.fromPin(“myChanges”); query.findInBackground(new FindCallback<Entity>() { public void done(List<Entity> scores, ParseException e) { for (Entity score in scores) { score.saveInBackground(); score.unpinInBackground(“myChanges”); } } });
At the core of many apps, there is a notion of user accounts that lets users access their information in a secure manner. We provide a specialized user class called ParseUser
that automatically handles much of the functionality required for user account management.
With this class, you'll be able to add user account functionality in your app.
ParseUser
is a subclass of the Entity
, and has all the same features, such as flexible schema, automatic persistence, and a key value interface. All the methods that are on Entity
also exist in ParseUser
. The difference is that ParseUser
has some special additions specific to user accounts.
ParseUser
has several properties that set it apart from Entity
:
We'll go through each of these in detail as we run through the various use cases for users. Keep in mind that if you set username
and email
using the setters, you do not need to set it using the put
method.
The first thing your app will do is probably ask the user to sign up. The following code illustrates a typical sign up:
ParseUser user = new ParseUser(); user.setUsername("my name"); user.setPassword("my pass"); user.setEmail("email@example.com"); // other fields can be set just like with Entity user.put("phone", "650-253-0000"); user.signUpInBackground(new SignUpCallback() { public void done(ParseException e) { if (e == null) { // Hooray! Let them use the app now. } else { // Sign up didn't succeed. Look at the ParseException // to figure out what went wrong } } });
This call will asynchronously create a new user in your Parse App. Before it does this, it checks to make sure that both the username and email are unique. Also, it securely hashes the password in the cloud. We never store passwords in plaintext, nor will we ever transmit passwords back to the client in plaintext.
Note that we used the signUpInBackground
method, not the saveInBackground
method. New ParseUser
s should always be created using the signUpInBackground
(or signUp
) method. Subsequent updates to a user can be done by calling save
.
The signUpInBackground
method comes in various flavors, with the ability to pass back errors, and also synchronous versions. As usual, we highly recommend using the asynchronous versions when possible, so as not to block the UI in your app. You can read more about these specific methods in our API docs.
If a signup isn't successful, you should read the error object that is returned. The most likely case is that the username or email has already been taken by another user. You should clearly communicate this to your users, and ask them try a different username.
You are free to use an email address as the username. Simply ask your users to enter their email, but fill it in the username property — ParseUser
will work as normal. We'll go over how this is handled in the reset password section.
Of course, after you allow users to sign up, you need be able to let them log in to their account in the future. To do this, you can use the class method logInInBackground
.
ParseUser.logInInBackground("Jerry", "showmethemoney", new LogInCallback() { public void done(ParseUser user, ParseException e) { if (user != null) { // Hooray! The user is logged in. } else { // Signup failed. Look at the ParseException to see what happened. } } });
Enabling email verification in an application's settings allows the application to reserve part of its experience for users with confirmed email addresses. Email verification adds the emailVerified
key to the ParseUser
object. When a ParseUser
's email
is set or modified, emailVerified
is set to false
. Parse then emails the user a link which will set emailVerified
to true
.
There are three emailVerified
states to consider:
true
- the user confirmed his or her email address by clicking on the link Parse emailed them. ParseUsers
can never have a true
value when the user account is first created.false
- at the time the ParseUser
object was last fetched, the user had not confirmed his or her email address. If emailVerified
is false
, consider calling fetch()
on the ParseUser
.ParseUser
was created when email verification was off or the ParseUser
does not have an email
.It would be bothersome if the user had to log in every time they open your app. You can avoid this by using the cached currentUser
object.
Whenever you use any signup or login methods, the user is cached on disk. You can treat this cache as a session, and automatically assume the user is logged in:
ParseUser currentUser = ParseUser.getCurrentUser(); if (currentUser != null) { // do stuff with the user } else { // show the signup or login screen }
You can clear the current user by logging them out:
ParseUser.logOut(); ParseUser currentUser = ParseUser.getCurrentUser(); // this will now be null
Being able to associate data and objects with individual users is highly valuable, but sometimes you want to be able to do this without forcing a user to specify a username and password.
An anonymous user is a user that can be created without a username and password but still has all of the same capabilities as any other ParseUser
. After logging out, an anonymous user is abandoned, and its data is no longer accessible.
You can create an anonymous user using ParseAnonymousUtils
:
ParseAnonymousUtils.logIn(new LogInCallback() { @Override public void done(ParseUser user, ParseException e) { if (e != null) { Log.d("MyApp", "Anonymous login failed."); } else { Log.d("MyApp", "Anonymous user logged in."); } } });
You can convert an anonymous user into a regular user by setting the username and password, then calling signUp()
, or by logging in or linking with a service like Facebook or Twitter. The converted user will retain all of its data. To determine whether the current user is an anonymous user, you can check ParseAnonymousUtils.isLinked()
:
if (ParseAnonymousUtils.isLinked(ParseUser.getCurrentUser())) { enableSignUpButton(); } else { enableLogOutButton(); }
Anonymous users can also be automatically created for you without requiring a network request, so that you can begin working with your user immediately when your application starts. When you enable automatic anonymous user creation at application startup, ParseUser.getCurrentUser()
will never be null
. The user will automatically be created in the cloud the first time the user or any object with a relation to the user is saved. Until that point, the user's object ID will be null
. Enabling automatic user creation makes associating data with your users painless. For example, in your Application.onCreate()
method, you might write:
ParseUser.enableAutomaticUser(); ParseUser.getCurrentUser().increment("RunCount"); ParseUser.getCurrentUser().saveInBackground();
If you’ve created your own authentication routines, or otherwise logged in a user on the server side, you can now pass the session token to the client and use the become
method. This method will ensure the session token is valid before setting the current user.
ParseUser.becomeInBackground("session-token-here", new LogInCallback() { public void done(ParseUser user, ParseException e) { if (user != null) { // The current user is now set to user. } else { // The token could not be validated. } } });
The ParseUser
class is secured by default. Data stored in a ParseUser
can only be modified by that user. By default, the data can still be read by any client. Thus, some ParseUser
objects are authenticated and can be modified, whereas others are read-only.
Specifically, you are not able to invoke any of the save
or delete
type methods unless the ParseUser
was obtained using an authenticated method, like logIn
or signUp
. This ensures that only the user can alter their own data.
The following illustrates this security policy:
ParseUser user = ParseUser.logIn("my_username", "my_password"); user.setUsername("my_new_username"); // attempt to change username user.saveInBackground(); // This succeeds, since the user was authenticated on the device // Get the user from a non-authenticated manner ParseQuery<ParseUser> query = ParseUser.getQuery(); query.getInBackground(user.getObjectId(), new GetCallback<ParseUser>() { public void done(ParseUser object, ParseException e) { object.setUsername("another_username"); // This will throw an exception, since the ParseUser is not authenticated object.saveInBackground(); } });
The ParseUser
obtained from getCurrentUser()
will always be authenticated.
If you need to check if a ParseUser
is authenticated, you can invoke the isAuthenticated()
method. You do not need to check isAuthenticated()
with ParseUser
objects that are obtained via an authenticated method.
The same security model that applies to the ParseUser
can be applied to other objects. For any object, you can specify which users are allowed to read the object, and which users are allowed to modify an object. To support this type of security, each object has an access control list, implemented by the ParseACL
class.
The simplest way to use a ParseACL
is to specify that an object may only be read or written by a single user. To create such an object, there must first be a logged in ParseUser
. Then, new ParseACL(user)
generates a ParseACL
that limits access to that user. An object's ACL is updated when the object is saved, like any other property. Thus, to create a private note that can only be accessed by the current user:
Entity privateNote = new Entity("Note"); privateNote.put("content", "This note is private!"); privateNote.setACL(new ParseACL(ParseUser.getCurrentUser())); privateNote.saveInBackground();
This note will then only be accessible to the current user, although it will be accessible to any device where that user is signed in. This functionality is useful for applications where you want to enable access to user data across multiple devices, like a personal todo list.
Permissions can also be granted on a per-user basis. You can add permissions individually to a ParseACL
using setReadAccess
and setWriteAccess
. For example, let's say you have a message that will be sent to a group of several users, where each of them have the rights to read and delete that message:
Entity groupMessage = new Entity("Message"); ParseACL groupACL = new ParseACL(); // userList is an Iterable<ParseUser> with the users we are sending this message to. for (ParseUser user : userList) { groupACL.setReadAccess(user, true); groupACL.setWriteAccess(user, true); } groupMessage.setACL(groupACL); groupMessage.saveInBackground();
You can also grant permissions to all users at once using setPublicReadAccess
and setPublicWriteAccess
. This allows patterns like posting comments on a message board. For example, to create a post that can only be edited by its author, but can be read by anyone:
Entity publicPost = new Entity("Post"); ParseACL postACL = new ParseACL(ParseUser.getCurrentUser()); postACL.setPublicReadAccess(true); publicPost.setACL(postACL); publicPost.saveInBackground();
To help ensure that your users' data is secure by default, you can set a default ACL to be applied to all newly-created ParseObjects
:
ParseACL.setDefaultACL(defaultACL, true);
In the code above, the second parameter to setDefaultACL tells Parse to ensure that the default ACL assigned at the time of object creation allows read and write access to the current user at that time. Without this setting, you would need to reset the defaultACL every time a user logs in or out so that the current user would be granted access appropriately. With this setting, you can ignore changes to the current user until you explicitly need to grant different kinds of access.
Default ACLs make it easy to create apps that follow common access patterns. An application like Twitter, for example, where user content is generally visible to the world, might set a default ACL such as:
ParseACL defaultACL = new ParseACL(); defaultACL.setPublicReadAccess(true); ParseACL.setDefaultACL(defaultACL, true);
For an application like Dropbox, where a user's data is only accessible by the user itself unless explicit permission is given, you would provide a default ACL where only the current user is given access:
ParseACL.setDefaultACL(new ParseACL(), true);
An application that logs data to Parse but doesn't provide any user access to that data would instead deny access to the current user while providing a restrictive ACL:
ParseACL.setDefaultACL(new ParseACL(), false);
Operations that are forbidden, such as deleting an object that you do not have write access to, result in a ParseException.OBJECT_NOT_FOUND
error code. For security purposes, this prevents clients from distinguishing which object ids exist but are secured, versus which object ids do not exist at all.
It's a fact that as soon as you introduce passwords into a system, users will forget them. In such cases, our library provides a way to let them securely reset their password.
To kick off the password reset flow, ask the user for their email address, and call:
<EMAIL_ADDRESS>new RequestPasswordResetCallback() { public void done(ParseException e) { if (e == null) { // An email was successfully sent with reset instructions. } else { // Something went wrong. Look at the ParseException to see what's up. } } });
This will attempt to match the given email with the user's email or username field, and will send them a password reset email. By doing this, you can opt to have users use their email as their username, or you can collect it separately and store it in the email field.
The flow for password reset is as follows:
Note that the messaging in this flow will reference your app by the name that you specified when you created this app on Parse.
To query for users, you need to use the special user query:
ParseQuery<ParseUser> query = ParseUser.getQuery(); query.whereEqualTo("gender", "female"); query.findInBackground(new FindCallback<ParseUser>() { public void done(List<ParseUser> objects, ParseException e) { if (e == null) { // The query was successful. } else { // Something went wrong. } } });
In addition, you can use get
to get a ParseUser
by id.
Associations involving a ParseUser
work right of the box. For example, let's say you're making a blogging app. To store a new post for a user and retrieve all their posts:
ParseUser user = ParseUser.getCurrentUser(); // Make a new post Entity post = new Entity("Post"); post.put("title", "My New Post"); post.put("body", "This is some great content."); post.put("user", user); post.saveInBackground(); // Find all posts by the current user ParseQuery<Entity> query = ParseQuery.getQuery("Post"); query.whereEqualTo("user", user); query.findInBackground(new FindCallback<Entity>() { ... });
The User class is a special class that is dedicated to storing ParseUser
objects. In the data browser, you'll see a little person icon next to the User class:
As your app grows in scope and user-base, you may find yourself needing more coarse-grained control over access to pieces of your data than user-linked ACLs can provide. To address this requirement, Parse supports a form of Role-based Access Control. Roles provide a logical way of grouping users with common access privileges to your Parse data. Roles are named objects that contain users and other roles. Any permission granted to a role is implicitly granted to its users as well as to the users of any roles that it contains.
For example, in your application with curated content, you may have a number of users that are considered "Moderators" and can modify and delete content created by other users. You may also have a set of users that are "Administrators" and are allowed all of the same privileges as Moderators, but can also modify the global settings for the application. By adding users to these roles, you can ensure that new users can be made moderators or administrators, without having to manually grant permission to every resource for each user.
We provide a specialized class called ParseRole
that represents these role objects in your client code.
ParseRole
is a subclass of Entity
, and has all of the same features, such as a flexible
schema, automatic persistence, and a key value interface. All the methods that are on Entity
also
exist on ParseRole
. The difference is that ParseRole
has some additions specific to management of roles.
ParseRole
has several properties that set it apart from Entity
:
The ParseRole
uses the same security scheme (ACLs) as all other objects on Parse, except that it requires an ACL
to be set explicitly. Generally, only users with greatly elevated privileges (e.g. a master user or Administrator) should be able
to create or modify a Role, so you should define its ACLs accordingly. Remember, if you give write-access to a
ParseRole
to a user, that user can add other users to the role, or even delete the role altogether.
To create a new ParseRole
, you would write:
// By specifying no write privileges for the ACL, we can ensure the role cannot be altered. ParseACL roleACL = new ParseACL(); roleACL.setPublicReadAccess(true); ParseRole role = new ParseRole("Administrator", roleACL); role.saveInBackground();
You can add users and roles that should inherit your new role's permissions through the "users" and "roles" relations on ParseRole
:
ParseRole role = new ParseRole(roleName, roleACL); for (ParseUser user : usersToAddToRole) { role.getUsers().add(user) } for (ParseRole childRole : rolesToAddToRole) { role.getRoles().add(childRole); } role.saveInBackground();
Take great care when assigning ACLs to your roles so that they can only be modified by those who should have permissions to modify them.
Now that you have created a set of roles for use in your application, you can use them with ACLs to define the privileges that their users will receive.
Each Entity
can specify a ParseACL
, which provides an access control list that indicates which users and roles
should be granted read or write access to the object.
Giving a role read or write permission to an object is straightforward. You can either use the ParseRole
:
ParseRole moderators = /* Query for some ParseRole */; Entity wallPost = new Entity("WallPost"); ParseACL postACL = new ParseACL(); postACL.setRoleWriteAccess(moderators); wallPost.setACL(postACL); wallPost.saveInBackground();
You can avoid querying for a role by specifying its name for the ACL:
Entity wallPost = new Entity("WallPost"); ParseACL postACL = new ParseACL(); postACL.setRoleWriteAccess("Moderators", true); wallPost.setACL(postACL); wallPost.save();
Role-based ParseACL
s can also be used when specifying default ACLs for your application, making it easy to protect
your users' data while granting access to users with additional privileges. For example, a moderated forum application might
specify a default ACL like this:
ParseACL defaultACL = new ParseACL(); // Everybody can read objects created by this user defaultACL.setPublicReadAccess(true); // Moderators can also modify these objects defaultACL.setRoleWriteAccess("Moderators"); // And the user can read and modify its own objects ParseACL.setDefaultACL(defaultACL, true);
As described above, one role can contain another, establishing a parent-child relationship between the two roles. The consequence of this relationship is that any permission granted to the parent role is implicitly granted to all of its child roles.
These types of relationships are commonly found in applications with user-managed content, such as forums. Some small subset of users are "Administrators", with the highest level of access to tweaking the application's settings, creating new forums, setting global messages, and so on. Another set of users are "Moderators", who are responsible for ensuring that the content created by users remains appropriate. Any user with Administrator privileges should also be granted the permissions of any Moderator. To establish this relationship, you would make your "Administrators" role a child role of "Moderators", like this:
ParseRole administrators = /* Your "Administrators" role */; ParseRole moderators = /* Your "Moderators" role */; moderators.getRoles().add(administrators); moderators.saveInBackground();
Parse provides an easy way to integrate Facebook with your application. The Facebook SDK can be used with our SDK, and is integrated with the ParseUser
class to make linking your users to their Facebook identities easy.
Using our Facebook integration, you can associate an authenticated Facebook user with a ParseUser
. With just a few lines of code, you'll be able to provide a "log in with Facebook" option in your app, and be able to save their data to Parse.
To start using Facebook with Parse, you need to:
Application.onCreate()
ParseFacebookUtils.initialize("YOUR FACEBOOK APP ID");
Facebook's Android SDK provides an enhanced login experience on devices that have
Facebook's official
Android app installed. This allows users of apps that support Facebook login to
sign in directly through the Facebook app, using credentials that are already on the device.
If the Facebook app is not installed, the default dialog-based authentication will be used.
Facebook calls this feature "Single sign-on," and requires you to override onActivityResult()
in your calling Activity
to invoke finishAuthentication()
.
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); ParseFacebookUtils.finishAuthentication(requestCode, resultCode, data); }
If your Activity
is already using onActivityResult()
, you
can avoid requestCode
collisions by calling the versions of link()
and logIn()
that take an activityCode
parameter and specifying
a code you know to be unique. Otherwise, a sensible default activityCode
will be used.
If you encounter any issues that are Facebook-related, a good resource is the official Facebook SDK for Android page.
Parse is compatible with v3.0 of the Facebook SDK for Android.
There are two main ways to use Facebook with your Parse users: (1) logging in as a Facebook user and creating a ParseUser
, or (2) linking Facebook to an existing ParseUser
.
ParseFacebookUtils
provides a way to allow your ParseUser
s to log in or sign up through Facebook. This is accomplished using the logIn()
method:
ParseFacebookUtils.logIn(this, new LogInCallback() { @Override public void done(ParseUser user, ParseException err) { if (user == null) { Log.d("MyApp", "Uh oh. The user cancelled the Facebook login."); } else if (user.isNew()) { Log.d("MyApp", "User signed up and logged in through Facebook!"); } else { Log.d("MyApp", "User logged in through Facebook!"); } } });
When this code is run, the following happens:
ParseUser
. If it's a new user
based on the Facebook ID, then that user is created.LogInCallback
is called with the user.In order to display the Facebook login dialogs and activities, the current Activity
must be provided (often, the current activity is this
when calling logIn()
from within the Activity
) as we have done above.
You may optionally provide a collection of strings that specifies what read permissions your app requires from the Facebook user. You may specify these strings yourself, or use the constants we've provided for you in the ParseFacebookUtils.Permissions
class. For example:
ParseFacebookUtils.logIn(Arrays.asList("email", Permissions.Friends.ABOUT_ME), this, new LogInCallback() { @Override public void done(ParseUser user, ParseException err) { // Code to handle login. } });
ParseUser
integration doesn't require any permissions to work out of the box (ie. null
or specifying no permissions is perfectly acceptable). When logging in, you can only use read permissions. See our documentation below about requesting additional permissions (read or publish). Read more about permissions on Facebook's developer guide.
If you want to associate an existing ParseUser
to a Facebook account, you can link it like so:
if (!ParseFacebookUtils.isLinked(user)) { ParseFacebookUtils.link(user, this, new SaveCallback() { @Override public void done(ParseException ex) { if (ParseFacebookUtils.isLinked(user)) { Log.d("MyApp", "Woohoo, user logged in with Facebook!"); } } }); }
The steps that happen when linking are very similar to log in. The difference is that on successful login,
the existing ParseUser
is updated with the Facebook information. Future logins via
Facebook will now log the user into their existing account.
If you want to unlink Facebook from a user, simply do this:
ParseFacebookUtils.unlinkInBackground(user, new SaveCallback() { @Override public void done(ParseException ex) { if (ex == null) { Log.d("MyApp", "The user is no longer associated with their Facebook account."); } } });
Or if you are handling threading yourself, you can call unlink()
directly like this:
try { ParseFacebookUtils.unlink(user); Log.d("MyApp", "The user is no longer associated with their Facebook account."); } catch (ParseException e) { }
As of v3.0 of the Facebook SDK, read and publish permissions must be requested separately.
ParseFacebookUtils.logIn()
and ParseFacebookUtils.link()
only
allow you to request read permissions. To request additional permissions, you may call
ParseFacebookUtils.getSession().requestNewReadPermissions()
or
ParseFacebookUtils.getSession().requestNewPublishPermissions()
. For more information
about requesting new permissions, please see Facebook's API documentation for these functions.
After successfully retrieving new permissions, please call ParseFacebookUtilities.saveLatestSessionData()
,
which will save any changes to the session token back to the ParseUser
and ensure that this session data
follows the user wherever it logs in.
The Facebook Android SDK provides a number of helper classes for interacting with Facebook's API. Generally, you will use the Request class to interact with Facebook on behalf of your logged-in user. You can read more about the Facebook SDK here.
Our library manages the user's Session
object for you. You can simply call
ParseFacebookUtils.getSession()
to access the session instance, which can
then be passed to Requests.
As with Facebook, Parse also provides an easy way to integrate Twitter authentication into your application. The Parse SDK provides a straightforward way to authorize and link a Twitter account to your ParseUser
s. With just a few lines of code, you'll be able to provide a "log in with Twitter" option in your app, and be able to save their data to Parse.
To start using Twitter with Parse, you need to:
Application.onCreate()
ParseTwitterUtils.initialize("YOUR CONSUMER KEY", "YOUR CONSUMER SECRET");
If you encounter any issues that are Twitter-related, a good resource is the official Twitter documentation.
There are two main ways to use Twitter with your Parse users: (1) logging in as a Twitter user and creating a ParseUser
, or (2) linking Twitter to an existing ParseUser
.
ParseTwitterUtils
provides a way to allow your ParseUser
s to log in or sign up through Twitter. This is accomplished using the logIn()
method:
ParseTwitterUtils.logIn(this, new LogInCallback() { @Override public void done(ParseUser user, ParseException err) { if (user == null) { Log.d("MyApp", "Uh oh. The user cancelled the Twitter login."); } else if (user.isNew()) { Log.d("MyApp", "User signed up and logged in through Twitter!"); } else { Log.d("MyApp", "User logged in through Twitter!"); } } });
When this code is run, the following happens:
ParseUser
. If it's a new user
based on the Twitter handle, then that user is created.LogInCallback
is called with the user.In order to display the Twitter login dialogs and activities, the current Context
must be provided (often, the current context is this
when calling logIn()
from within the Activity
) as we have done above.
If you want to associate an existing ParseUser
with a Twitter account, you can link it like so:
if (!ParseTwitterUtils.isLinked(user)) { ParseTwitterUtils.link(user, this, new SaveCallback() { @Override public void done(ParseException ex) { if (ParseTwitterUtils.isLinked(user)) { Log.d("MyApp", "Woohoo, user logged in with Twitter!"); } } }); }
The steps that happen when linking are very similar to log in. The difference is that on successful login,
the existing ParseUser
is updated with the Twitter information. Future logins via
Twitter will now log the user into their existing account.
If you want to unlink Twitter from a user, simply do this:
ParseTwitterUtils.unlinkInBackground(user, new SaveCallback() { @Override public void done(ParseException ex) { if (ex == null) { Log.d("MyApp", "The user is no longer associated with their Twitter account."); } } });
Our SDK provides a straightforward way to sign your API HTTP requests to the Twitter REST API when your app has a Twitter-linked ParseUser
. To make a request through our API, you can use the Twitter
singleton provided by ParseTwitterUtils
:
HttpClient client = new DefaultHttpClient(); HttpGet verifyGet = new HttpGet( "https://api.twitter.com/1/account/verify_credentials.json"); ParseTwitterUtils.getTwitter().signRequest(verifyGet); HttpResponse response = client.execute(verifyGet);
Cloud Functions can be called from Android using ParseCloud
.
For example, to call the Cloud Function named hello
:
ParseCloud.callFunctionInBackground("hello", new HashMap<String, Object>(), new FunctionCallback<String>() { void done(String result, ParseException e) { if (e == null) { // result is "Hello world!" } } });
Parse allows you to associate real-world latitude and longitude coordinates with an object. Adding a ParseGeoPoint
to a Entity
allows queries to take into account the proximity of an object to a reference point. This allows you to easily do things like find out what user is closest to another user or which places are closest to a user.
To associate a point with an object you first need to create a ParseGeoPoint
. For example, to create a point with latitude of 40.0 degrees and -30.0 degrees longitude:
ParseGeoPoint point = new ParseGeoPoint(40.0, -30.0);
This point is then stored in the object as a regular field.
placeObject.put("location", point);
Now that you have a bunch of objects with spatial coordinates, it would be nice to find out which objects are closest to a point. This can be done by adding another restriction to ParseQuery
using whereNear
. Getting a list of ten places that are closest to a user may look something like:
ParseGeoPoint userLocation = (ParseGeoPoint) userObject.get("location"); ParseQuery<Entity> query = ParseQuery.getQuery("PlaceObject"); query.whereNear("location", userLocation); query.setLimit(10); query.findInBackground(new FindCallback<Entity>() { ... });
At this point nearPlaces
will be an array of objects ordered by distance
(nearest to farthest) from userLocation
.
Note that if an additional orderByAscending()
/orderByDescending()
constraint is applied,
it will take precedence over the distance ordering.
To limit the results using distance, check out whereWithinKilometers
, whereWithinMiles
, and whereWithinRadians
.
It's also possible to query for the set of objects that are contained within a particular area. To find the objects in a rectangular bounding box, add the whereWithinGeoBox
restriction to your ParseQuery
.
ParseGeoPoint southwestOfSF = new ParseGeoPoint(37.708813, -122.526398); ParseGeoPoint northeastOfSF = new ParseGeoPoint(37.822802, -122.373962); ParseQuery<Entity> query = ParseQuery.getQuery("PizzaPlaceObject"); query.whereWithinGeoBox("location", southwestOfSF, northeastOfSF); query.findInBackground(new FindCallback<Entity>() { ... });
At the moment there are a couple of things to watch out for:
At the end of the day, users of your application will be interacting with Android UI components. We provide several UI widgets to make working with Parse data easier.
If you are using Parse to manage users in your mobile app, you are already familiar with the ParseUser
class. At some point in your app, you might want to present a screen to log in your ParseUser
. Parse provides an open-source ParseLoginUI library project that does exactly this. Please note ParseLoginUI is not included with the Parse Android SDK; you need to import this library project from our Git repository into your Android Studio or Eclipse project. Here is an example for how to import a library project.
This library project contains an Android login activity that is ultra-customizable and easy to integrate with your app. You can configure the look and feel of the login screens by either specifying XML configurations or constructing an Intent in code. In this guide, we first provide several ways to integrate with the login library. Then, we describe in detail how to customize the login screens.
Screens of the ParseLoginUI library
The login library integration is simple. Our library exposes an activity-level API that lets you launch the login library activity (ParseLoginActivity
) to obtain a ParseUser
. ParseLoginActivity
will guide the user through several screens to log in, sign up, or reset their password. If the user resets their password by email, they are taken back to the login screen.
The user will also see helpful toast messages if they provide invalid input (e.g. logging in with an incorrect password or signing up with a username that's already taken).
To include ParseLoginActivity
in your app, import the ParseLoginUI library project, and add the following into your AndroidManifest.xml
:
<activity android:name="com.parse.ui.ParseLoginActivity" android:label="@string/app_name" android:launchMode="singleTop"> <meta-data android:name="com.parse.ui.ParseLoginActivity.PARSE_LOGIN_ENABLED" android:value="true"/> </activity>
Then, you can launch ParseLoginActivity
from your own activity by calling:
ParseLoginBuilder builder = new ParseLoginBuilder(MyActivity.this); startActivityForResult(builder.build(), 0);
When ParseLoginActivity
finishes, your caller activity will receive either:
RESULT_OK
ParseUser.getCurrentUser()
will be populated.RESULT_CANCELLED
We've provided a sample app, ParseLoginSampleBasic for this use case. This sample app is a simple profile viewer. If you are not logged in, SampleProfileActivity
prompts you to log in. Clicking on the login button in SampleProfileActivity
launches the ParseLoginActivity
, which prompts the user for login credentials. If the user successfully logs in, ParseLoginActivity
will automatically finish itself and return RESULT_OK
. Then, SampleProfileActivity
will display the user's name and a logout button.
Basic login integration
This basic integration case works well if your caller activity is designed to function regardless of whether there is a valid current user. For example, a restaurant reviews app may allow the user to browse restaurants even when the user is not logged in. If the user does log in, the app could provide a more personalized experience on that same screen.
If some parts or all of your app cannot function without a valid ParseUser
, you can protect these parts of your app with a ParseLoginDispatchActivity
(supplied in this library project). This dispatch activity acts like a gatekeeper; it automatically launches ParseLoginActivity
if no user is logged in, or launches the protected activity if a user is already logged in. To use this, you subclass ParseLoginDispatchActivity
and specify what protected activity to launch.
public class SampleDispatchActivity extends ParseLoginDispatchActivity { @Override protected Class<?> getTargetClass() { return SampleProfileActivity.class; } }
We've provided another sample app, ParseLoginSampleWithDispatchActivity for this use case. The SampleProfileActivity
in this app cannot function without a logged-in user, so it is protected by SampleDispatchActivity
.
Login with DispatchActivity protecting the entire app
The dispatch activity does not necessarily need to be the first activity at app launch. You can launch the dispatch activity from any activity. When your protected activity finishes, the dispatch activity will automatically forward the result code to your caller activity.
Let's revisit the restaurant reviews app again. You might have a comment activity that requires a user. You can protect this activity behind a dispatch activity. The main restaurant listing activity (supports either user or no user), can launch the dispatch activity when the user presses the comment button. If no user is logged in, the dispatch activity will start ParseLoginActivity
to obtain a ParseUser. If the user refuses to log in, they will be gracefully taken back to the restaurant listings activity. In the restaurant listings activity, you can always call ParseUser.getCurrentUser()
to determine whether the user logged in.
Login with DispatchActivity protecting parts of the app
There are three ways to customize the login library:
AndroidManifest.xml
- This is most recommended because it allows you to customize the login experience without writing any code, and it makes the login experience consistent regardless which activity in your app launches ParseLoginActivity
.ParseLoginBuilder
- If you love writing code to do everything.We provide the following options for easily customizing the ParseLoginActivity in your app's AndroidManifest.xml:
APP_LOGO
Drawable
resource for app logo.PARSE_LOGIN_ENABLED
Boolean
for whether to enable the Parse username/password login (default = false)PARSE_LOGIN_BUTTON_TEXT
String
to display on the login button (default = “Log in”)PARSE_SIGNUP_BUTTON_TEXT
String
to display on the signup button on the login screen (default = "Sign up")PARSE_LOGIN_HELP_TEXT
String
to display on the password-reset link (default = "Forgotten password")PARSE_LOGIN_INVALID_CREDENTIALS_TEXT
String
to show on the toast when the user login fails (default = "The username and password you entered don't match")PARSE_LOGIN_EMAIL_AS_USERNAME
Boolean
for whether to prompt for the user's email as the username on the login and signup form (default = false)PARSE_SIGNUP_MIN_PASSWORD_LENGTH
Integer
for the minimum required password length on the signup form (default = 6)PARSE_SIGNUP_SUBMIT_BUTTON_TEXT
String
to display on the submit button on the signup screen (default = "Submit")FACEBOOK_LOGIN_ENABLED
Boolean
for whether to show the Facebook login button (default = false)FACEBOOK_LOGIN_BUTTON_TEXT
String
to display on the Facebook login button (default = "Log in with Facebook")FACEBOOK_LOGIN_PERMISSIONS
String
array resource containing requested Facebook permissions (default = empty)TWITTER_LOGIN_ENABLED
Boolean
for whether to show the Twitter login button (default = false)TWITTER_LOGIN_BUTTON_TEXT
String
to display on the Twitter login button (default = "Log in with Twitter")Please note that PARSE_LOGIN_ENABLED
, FACEBOOK_LOGIN_ENABLED
, and TWITTER_LOGIN_ENABLED
are all false by default. You need to explicitly set them to true for those components to show up on the screen. For APP_LOGO
and FACEBOOK_LOGIN_PERMISSIONS
, make sure you use android:resource
instead of android:value
.
Example configuration:
<activity android:name="com.parse.ui.ParseLoginActivity" android:label="@string/my_app_name" android:launchMode="singleTop"> <!-- We reference a drawable resource here, so we must use android:resource --> <meta-data android:name="com.parse.ui.ParseLoginActivity.APP_LOGO" android:resource="@drawable/my_app_logo"/> <!-- For these non-resource options, use android:value --> <meta-data android:name="com.parse.ui.ParseLoginActivity.PARSE_LOGIN_ENABLED" android:value="true"/> <meta-data android:name="com.parse.ui.ParseLoginActivity.PARSE_LOGIN_EMAIL_AS_USERNAME" android:value="true"/> <meta-data android:name="com.parse.ui.ParseLoginActivity.PARSE_LOGIN_HELP_TEXT" android:value="@string/password_reset_text"/> <meta-data android:name="com.parse.ui.ParseLoginActivity.MIN_PASSWORD_LENGTH" android:value="8"/> <meta-data android:name="com.parse.ui.ParseLoginActivity.FACEBOOK_LOGIN_ENABLED" android:value="true"/> <!-- We reference a string-array resource here, so we must use android:resource --> <meta-data android:name="com.parse.ui.ParseLoginActivity.FACEBOOK_LOGIN_PERMISSIONS" android:resource="@array/my_facebook_permissions"/> </activity>
For the Facebook permission array, you also need to have this in your res/values/strings.xml
:
<resources> <string-array name="my_facebook_permissions"> <item>public_profile</item> <item>user_friends</item> </string-array> </resources>
You can configure the ParseLoginActivity
by code using the ParseLoginBuilder. You specify the options on the builder, and then call build()
to generate an Intent
that can be used to start the ParseLoginActivity
. We've provided a sample app, ParseLoginSampleCodeCustomization demonstrating this use case. The options in ParseLoginBuilder
are the same as those in activity metadata customization. If you specify options in both code and activity metadata, the options in code take precedence.
ParseLoginBuilder builder = new ParseLoginBuilder(ProfileActivity.this); Intent parseLoginIntent = builder.setAppLogo(R.drawable.my_app_logo) .setParseLoginEnabled(true) .setParseLoginButtonText("Go") .setParseSignupButtonText("Register") .setParseLoginHelpText("Forgot password?") .setParseLoginInvalidCredentialsToastText("You email and/or password is not correct") .setParseLoginEmailAsUsername(true) .setParseSignupSubmitButtonText("Submit registration") .setFacebookLoginEnabled(true) .setFacebookLoginButtonText("Facebook") .setFacebookLoginPermissions(Arrays.asList("public_profile", "user_friends")) .setTwitterLoginEnabled(true) .setTwitterLoginButtontext("Twitter") .build(); startActivityForResult(parseLoginIntent, 0);
You can override any layout resources by having files with the same name as those in the ParseLoginUI library project. This is useful if you want to add a background image, or reposition the login components on the screen. The Android build process will automatically merge resource files with the same name, giving your app project's files precedence. The top-level layout files are:
com_parse_ui_parse_login_fragment.xml
com_parse_ui_parse_signup_fragment.xml
ParseSignupFragment
to copy that data into the ParseUser
object.com_parse_ui_parse_login_help_fragment.xml
We've provided another sample app, ParseLoginSampleLayoutOverride showing how to do this. This sample app only has a Facebook login button in com_parse_ui_parse_login_fragment.xml
, and adds a background image to the login screens.
To display collections of data, we provide an implementation of Adapter
in the Parse Android SDK.
Instead of using a basic ListAdapter
backed by a static array of objects,
our ParseQueryAdapter
provides a layer of abstraction
and allows you to easily display data from one of your Parse classes
in your AdapterView
of choice (e.g. ListView
or GridView
).
To use a ParseQueryAdapter
to display data in an Activity
,
follow the steps outlined below in your Activity
's onCreate
:
ParseQueryAdapter
.
View
s to be displayed, and more).Adapter
on your AdapterView
with setAdapter()
.When the AdapterView is attached to the window,
your ParseQueryAdapter
will automatically fetch the first set of data.
This subclass simplifies the code that would otherwise be involved with:
Consider the following code, which sets up a very simple ParseQueryAdapter
to display data in a ListView
.
You can be up and running with a functional ListView
full of data
with very little configuration.
// Inside an Activity public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Uses a layout with a ListView (id: "listview"), which uses our Adapter. setContentView(R.layout.main); ParseQueryAdapter<Entity> adapter = new ParseQueryAdapter<Entity>(this, "Instrument"); adapter.setTextKey("name"); adapter.setImageKey("photo"); ListView listView = (ListView) findViewById(R.id.listview); listView.setAdapter(adapter); }
This view will display a list of Instruments by name. Notice all the code that we're not writing: we can skip the logic to fetch each consecutive page of results, to manually update and maintain the backing data array, to download images in the background and set the image data on UI widgets, and to handle touch events to trigger loading the next page of results.
The ParseQueryAdapter
can be configured to customize
what data to use, how to display it,
and what to do before and after it's been fetched.
Read on to see what you can do,
and how to tweak a ParseQueryAdapter
to fit all of your needs.
By default, the simplest ParseQueryAdapter
constructor takes
a Context
and a Parse class name.
All Entity
s in that class are then fetched and
displayed in order of their createdAt
timestamps.
To change this behavior, we drew from the functionality of an ArrayAdapter
:
but instead of taking in a vanilla array of objects to be displayed by the adapter,
ParseQueryAdapter
can also take a QueryFactory
class
which returns a ParseQuery
you define.
Pass that into the constructor,
and the adapter will then use that query to determine which objects to fetch and display.
See below for an example setting up a ParseQueryAdapter
to display only
punk and metal bands with four or more members,
ordered by number of records sold:
ParseQueryAdapter<Entity> adapter = new ParseQueryAdapter<Entity>(this, new ParseQueryAdapter.QueryFactory<Entity>() { public ParseQuery<Entity> create() { // Here we can configure a ParseQuery to our heart's desire. ParseQuery query = new ParseQuery("Band"); query.whereContainedIn("genre", Arrays.asList({ "Punk", "Metal" })); query.whereGreaterThanOrEqualTo("memberCount", 4); query.orderByDescending("albumsSoldCount"); return query; } });
The default layout for the individual View
s in your AdapterView
is a simple LinearLayout
with a ParseImageView
and a TextView
.
If setTextKey(String)
is used with the ParseQueryAdapter
,
its parameter will be used to select which key on your Entity
is displayed in the TextView
.
Similarly, if setImageKey(String)
is used,
its parameter will be used to determine the image displayed in the ImageView.
One way to customize the rows is to
override getItemView(Entity, View, ViewGroup)
or getNextPageView(View, ViewGroup)
and call the superclass's implementation of the appropriate method to do the heavy lifting.
If you provide your own layout to the superclass's implementation, note that
getItemView(Entity, View, ViewGroup)
and getNextPageView(View, ViewGroup)
expect a TextView
(id: android.R.id.text1
) if the textKey
is set
and a ParseImageView
(id: android.R.id.icon
) if the imageKey
is set.
Here, we inflate and configure a layout of our own,
with a TextView
, a ParseImageView
,
and an extra "description" TextView
(id: R.id.description
):
@Override public View getItemView(Entity object, View v, ViewGroup parent) { if (v == null) { v = View.inflate(getContext(), R.layout.adapter_item, null); } // Take advantage of ParseQueryAdapter's getItemView logic for // populating the main TextView/ImageView. // The IDs in your custom layout must match what ParseQueryAdapter expects // if it will be populating a TextView or ImageView for you. super.getItemView(object, v, parent); // Do additional configuration before returning the View. TextView descriptionView = (TextView) v.findViewById(R.id.description); descriptionView.setText(object.getString("description")); return v; }
Another way to customize the rows is
to have complete control over the look of the rows
by overriding ParseQueryAdapter
's methods and
ignoring the superclass's implementation entirely.
In this example, our item views are simply rows where the color is defined by the Entity
:
@Override public View getItemView(Entity object, View v, ViewGroup parent) { if (v == null) { v = View.inflate(getContext(), R.layout.adapter_item, null); } v.setBackgroundColor(object.getInt("color")); return v; } @Override public View getNextPageView(View v, ViewGroup parent) { if (v == null) { // R.layout.adapter_next_page contains an ImageView with a custom graphic // and a TextView. v = View.inflate(getContext(), R.layout.adapter_next_page, null); } TextView textView = (TextView) v.findViewById(R.id.nextPageTextViewId); textView.setText("Loaded " + getCount() + " rows. Get more!"); return v; }
ParseQueryAdapter
makes it simple to display remote images.
By calling setImageKey(String)
, you can pass in a key name on your Entity
which should contain a ParseFile
containing an image to be fetched from Parse
and loaded into the ParseImageView
of the corresponding row.
The image will download asynchronously,
and the appropriate ParseImageView
will be updated in the background.
As the user scrolls and rows are recycled by the adapter,
images will be fetched as rows become visible and assigned Entity
s.
You can define a placeholder image to be used when the image fetch has not yet completed.
Call setPlaceholder(Drawable)
on your ParseQueryAdapter
to use the specified Drawable
as a fallback image.
We expose two hooks in the data lifecycle of the Adapter for you to execute custom logic — right before we query Parse for your data and right after the fetched objects have been loaded from the query. These methods are particularly useful for toggling some loading UI.
An OnQueryLoadListener
can be set via setOnQueryLoadListener(OnQueryLoadListener)
,
which provides onLoading()
and onLoaded(List
methods for implementation.
Pagination ensures that the table only gets one page of objects at a time.
You can set the number of objects are in a page by calling setObjectsPerPage(int)
.
The query is automatically altered to apply pagination,
and a pagination row appears at the bottom of the AdapterView
to allow users to load the next page.
Pagination is turned on by default.
To turn it off, call setPaginationEnabled(false)
.
With pagination turned off, the ParseQueryAdapter
will use
the default ParseQuery
limit of 100 items.
When the AdapterView
that your ParseQueryAdapter
is set on is attached to a window,
the ParseQueryAdapter
's loadObjects()
method is automatically called,
triggering the fetching of the first page of results.
To disable this behavior (perhaps to delay the fetching of data, or run some custom logic ahead of time),
just call setAutoload(false)
and
call loadObjects()
manually if autoload is disabled.
Many of the methods on Entity
, including save()
, delete()
, and get()
will throw a ParseException
on an invalid request, such as deleting or editing an object that no longer exists in the cloud, or when there is a network failure preventing communication with the Parse Cloud. You will need to catch and deal with these exceptions.
For more details, look at the Android API.
In addition to coding securely, please review the settings pages for your applications to select options that will restrict access to your applications as much as is appropriate for your needs. For example, if users should be unable to log in without a Facebook account linked to their application, disable all other login mechanisms. Specify your Facebook application IDs, Twitter consumer keys, and other such information to enable server-side validation of your users' login attempts.