0% found this document useful (0 votes)
59 views

Android Developers Guide - Parse

The document provides guidance on getting started with the Parse Android SDK for building mobile apps. It discusses installing the SDK dependency, initializing Parse with your app and server configuration, and provides examples of saving and retrieving ParseObjects to represent and store data in the cloud. Key concepts covered include using ParseObjects to store schemaless JSON-like data with custom classes and keys, how objects are automatically given IDs and timestamps, and retrieving objects with queries by object ID.

Uploaded by

dksingh369
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
59 views

Android Developers Guide - Parse

The document provides guidance on getting started with the Parse Android SDK for building mobile apps. It discusses installing the SDK dependency, initializing Parse with your app and server configuration, and provides examples of saving and retrieving ParseObjects to represent and store data in the cloud. Key concepts covered include using ParseObjects to store schemaless JSON-like data with custom classes and keys, how objects are automatically given IDs and timestamps, and retrieving objects with queries by object ID.

Uploaded by

dksingh369
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 171

7/26/22, 5:13 PM Android Developers Guide | Parse

Android
Guide Browse

Getting Started

Getting Started
Note that we support Android 4.0 and higher. You can
also check out our API Reference for more detailed
information about our SDK.

Installation
Step 1: Download Parse-SDK-Android

Dependency
Add this in your root build.gradle file (not your
module build.gradle file):

allprojects { 📋
repositories {
...
maven { url
"https://jitpack.io" }
}
}

Then, add the library to your project build.gradle

dependencies { 📋
implementation "com.github.parse-
community.Parse-SDK-

https://docs.parseplatform.org/android/guide/#users 1/171
7/26/22, 5:13 PM Android Developers Guide | Parse
Android:parse:latest.version.here"
}

JitPack 4.0.0
with the latest version being

Step 2: Setup Parse Initialize Parse using your server


configuration:

import com.parse.Parse; 📋
import android.app.Application;

public class App extends Application {


@Override
public void onCreate() {
super.onCreate();
Parse.initialize(new
Parse.Configuration.Builder(this)
.applicationId("YOUR_APP_ID")
// if defined
.clientKey("YOUR_CLIENT_KEY")
.server("http://localhost:1337/parse/")
.build()
);
}
}

The custom Application class must be registered in


AndroidManifest.xml :

<application 📋
android:name=".App"
...>
...
</application>

Note that if you are testing with a server using http ,


you will need to add
android:usesCleartextTraffic="true" to your above
<application> definition, but you should only do this
while testing and should use https for your final
product.

Want to contribute to this doc? Edit this section.

Objects
The ParseObject
https://docs.parseplatform.org/android/guide/#users 2/171
7/26/22, 5:13 PM Android Developers Guide | Parse

Storing data on Parse is built around the ParseObject .


Each ParseObject 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 ParseObject . 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 ParseObject 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 ParseObject 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.

Saving Objects
Let’s say you want to save the GameScore described
above to your Parse Server. The interface is similar to a
Map , plus the saveInBackground method:

ParseObject gameScore = new 📋


ParseObject("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:

https://docs.parseplatform.org/android/guide/#users 3/171
7/26/22, 5:13 PM Android Developers Guide | Parse

{ 📋
"objectId": "xWMyZ4YEGZ",
"score": 1337,
"playerName": "Sean Plott",
"cheatMode": false,
"createdAt":"2022-01-01T12:23:45.678Z",
"updatedAt":"2022-01-01T12:23:45.678Z"
}

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
ParseObject until a save operation has completed.

Retrieving Objects
Saving data to the cloud is fun, but it’s even more fun to
get that data out again. If you have the objectId ,
which is available once the ParseObject been
uploaded to the server, you can retrieve the whole
ParseObject using a ParseQuery :

ParseQuery<ParseObject> query = 📋
ParseQuery.getQuery("GameScore");
query.getInBackground("xWMyZ4YEGZ", new
GetCallback<ParseObject>() {
public void done(ParseObject object,
ParseException e) {
if (e == null) {
// object will be your game score
} else {
// something went wrong
}
}
});

To get the values out of the ParseObject , there’s a


getX method for each data type:

https://docs.parseplatform.org/android/guide/#users 4/171
7/26/22, 5:13 PM Android Developers Guide | Parse

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 four special values have their own accessors:

String objectId = gameScore.getObjectId(); 📋


Date updatedAt = gameScore.getUpdatedAt();
Date createdAt = gameScore.getCreatedAt();
ParseACL acl = gameScore.getACL();

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<ParseObject>() {
public void done(ParseObject object,
ParseException e) {
if (e == null) {
// Success!
} else {
// Failure!
}
}
});

The code in the GetCallback will be run on the main


thread.

The Local Datastore


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

https://docs.parseplatform.org/android/guide/#users 5/171
7/26/22, 5:13 PM Android Developers Guide | Parse

the local datastore is enabled, you can store an object


by pinning it.

ParseObject gameScore = new 📋


ParseObject("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.

Retrieving Objects from the Local


Datastore
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<ParseObject> query = 📋
ParseQuery.getQuery("GameScore");
query.fromLocalDatastore();
query.getInBackground("xWMyZ4YEGZ", new
GetCallback<ParseObject>() {
public void done(ParseObject 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.

ParseObject object = 📋
ParseObject.createWithoutData("GameScore",
"xWMyZ4YEGZ");
object.fetchFromLocalDatastoreInBackground(new
GetCallback<ParseObject>() {
https://docs.parseplatform.org/android/guide/#users 6/171
7/26/22, 5:13 PM Android Developers Guide | Parse
public void done(ParseObject object,
ParseException e) {
if (e == null) {
// object will be your game score
} else {
// something went wrong
}
}
});

Unpinning Objects
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(); 📋

Saving Objects Offline


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.

ParseObject gameScore = new 📋


ParseObject("GameScore");
gameScore.put("score", 1337);
gameScore.put("playerName", "Sean Plott");
gameScore.put("cheatMode", false);
gameScore.saveEventually();

Updating Objects

https://docs.parseplatform.org/android/guide/#users 7/171
7/26/22, 5:13 PM Android Developers Guide | Parse

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 ParseObject using a ParseQuery and
update its data:

ParseQuery<ParseObject> query = 📋
ParseQuery.getQuery("GameScore");

// Retrieve the object by id


query.getInBackground("xWMyZ4YEGZ", new
GetCallback<ParseObject>() {
public void done(ParseObject 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 your Parse Server.
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.

Counters
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) .

https://docs.parseplatform.org/android/guide/#users 8/171
7/26/22, 5:13 PM Android Developers Guide | Parse

Arrays
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.

Deleting Objects
To delete an object from your Parse Server:

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:

https://docs.parseplatform.org/android/guide/#users 9/171
7/26/22, 5:13 PM Android Developers Guide | Parse

// After this, the playerName field will be 📋


empty
myObject.remove("playerName");

// Saves the field deletion to your Parse


Server
myObject.saveInBackground();

Relational Data
Objects can have relationships with other objects. To
model this behavior, any ParseObject can be used as a
value in other ParseObject 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 📋


ParseObject myPost = new ParseObject("Post");
myPost.put("title", "I'm Hungry");
myPost.put("content", "Where should we go for
lunch?");

// Create the comment


ParseObject myComment = new
ParseObject("Comment");
myComment.put("content", "Let's do
Sushirrito.");

// Add a relation between the Post and Comment


myComment.put("post", 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("post",
ParseObject.createWithoutData("Post",
"1zEcyElZ80"));

By default, when fetching an object, related


ParseObject s are not fetched. These objects’ values
cannot be retrieved until they have been fetched like so:

https://docs.parseplatform.org/android/guide/#users 10/171
7/26/22, 5:13 PM Android Developers Guide | Parse

fetchedComment.getParseObject("post") 📋
.fetchIfNeededInBackground(new
GetCallback<ParseObject>() {
public void done(ParseObject 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<ParseObject> , except that you don’t need to
download all the ParseObject s in a relation at once.
This allows ParseRelation to scale to many more
objects than the List<ParseObject> 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<ParseObject> 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<ParseObject>() {
void done(List<ParseObject> results,
ParseException e) {
if (e != null) {
// There was an error
} else {
// results have all the Posts the
current user liked.
}

https://docs.parseplatform.org/android/guide/#users 11/171
7/26/22, 5:13 PM Android Developers Guide | Parse
}
});

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<ParseObject> 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<ParseObject> for querying purposes,
so any queries you can do on lists of objects (other than
include ) you can do on ParseRelation .

Data Types
So far we’ve used values with type String , Integer ,
boolean , and ParseObject . Parse also supports
float , java.util.Date , and JSONObject.NULL .

You can nest JSONObject and JSONArray objects to


store more structured data within a single
ParseObject . Overall, the following types are allowed
for each field in your object:

String => String

Number => primitive numeric values such as


int s, double s, long s, or float s

Bool => boolean

Array => JSONArray

Object => JSONObject

Date => java.util.Date

File => ParseFile

Pointer => other ParseObject

Relation => ParseRelation

Null => JSONObject.NULL

https://docs.parseplatform.org/android/guide/#users 12/171
7/26/22, 5:13 PM Android Developers Guide | Parse

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);

ParseObject bigObject = new


ParseObject("BigObject");
bigObject.put("myNumber", myNumber);
bigObject.put("myString", myString);
bigObject.put("myDate", myDate);
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 on ParseObject . We
recommend you use ParseFile s to store images,
documents, and other types of files. You can do so by
instantiating a ParseFile object and setting it on a
field. See Files for more details.

For more information about how Parse handles data,


check out our documentation on Data.

Subclasses
Parse is designed to get you up and running as quickly
as possible. You can access all of your data using the
ParseObject 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:

ParseObject shield = new ParseObject("Armor");📋


shield.put("displayName", "Wooden Shield");
shield.put("fireproof", false);
shield.put("rupees", 50);

Into this:

https://docs.parseplatform.org/android/guide/#users 13/171
7/26/22, 5:13 PM Android Developers Guide | Parse

Armor shield = new Armor(); 📋


shield.setDisplayName("Wooden Shield");
shield.setFireproof(false);
shield.setRupees(50);

SUBCLASSING PARSEOBJECT

To create a ParseObject subclass:

1 Declare a subclass which extends ParseObject .

2 Add a @ParseClassName annotation. Its value


should be the string you would pass into the
ParseObject constructor, and makes all future
class name references unnecessary.

3 Ensure that your subclass has a public default (i.e.


zero-argument) constructor. You must not
modify any ParseObject fields in this
constructor.

4 Call
ParseObject.registerSubclass(YourClass.cla
ss) in your Application constructor before
calling Parse.initialize() . The following code
successfully implements and registers the Armor
subclass of ParseObject :

// Armor.java 📋
import com.parse.ParseObject;
import com.parse.ParseClassName;

@ParseClassName("Armor")
public class Armor extends ParseObject {
}

// App.java
import com.parse.Parse;
import android.app.Application;

public class App extends Application {


@Override
public void onCreate() {
super.onCreate();

ParseObject.registerSubclass(Armor.class);
Parse.initialize(this);
}
}

ACCESSORS, MUTATORS, AND METHODS

Adding methods to your ParseObject subclass helps


encapsulate logic about the class. You can keep all your
logic about a subject in one place rather than using

https://docs.parseplatform.org/android/guide/#users 14/171
7/26/22, 5:13 PM Android Developers Guide | Parse

separate classes for business logic and


storage/transmission logic.

You can add accessors and mutators for the fields of


your ParseObject 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 ParseObject {
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);
}
}

INITIALIZING SUBCLASSES

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 ParseObject , which will be used
https://docs.parseplatform.org/android/guide/#users 15/171
7/26/22, 5:13 PM Android Developers Guide | Parse

throughout the Parse SDK to create strongly-typed


instances of your subclass.

To create a reference to an existing object, use


ParseObject.createWithoutData() :

Armor armorReference = 📋
ParseObject.createWithoutData(Armor.class,
armor.getObjectId());

QUERIES ON SUBCLASSES

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) {
// ...
}
}
});

Parcelable
As most public facing components of the SDK,
ParseObject implements the Parcelable interface.
This means you can retain a ParseObject during
configuration changes, or pass objects to other
components through Bundle s. To achieve this,
depending on the context, use either
Parcel#writeParcelable(Parcelable, int) or
Bundle#putParcelable(String, Parcelable) . For
instance, in an Activity ,

private ParseObject object; 📋


@Override
protected void onSaveInstanceState(Bundle
outState) {
super.onSaveInstanceState(outState);
outState.putParcelable("object", object);

https://docs.parseplatform.org/android/guide/#users 16/171
7/26/22, 5:13 PM Android Developers Guide | Parse
}

@Override
protected void onCreate(@Nullable Bundle
savedInstanceState) {
if (savedInstanceState != null) {
object = (ParseObject)
savedInstanceState.getParcelable("object");
}
}

That’s it. ParseObject will parcel its internal state,


along with unsaved children and dirty changes. There
are, however, a few things to be aware of when
parceling objects that have ongoing operations, like
save or delete. The SDK behavior differs depending on
whether you have enabled the Local Datastore.

PARCELING WITH LOCAL DATASTORE ENABLED

When the Local Datastore is enabled, parceling a


ParseObject is a safe operation even if there are
ongoing save or delete operations. Thanks to LDS, the
same instance is returned when unparceling (unless
something happens in the middle, in which case the
SDK behaves as if LDS was disabled, see below).

This means that the ParseObject is internally notified


about the operation results, whether it’s successful or
not. There is, however, no way to register external
callbacks ( SaveCallback or DeleteCallback ) for
these tasks, other than the ones you have already
registered at the moment of saving / deleting the
source instance.

PARCELING WITH LOCAL DATASTORE DISABLED

When the Local Datastore is disabled, and the parceled


ParseObject has ongoing operations that haven’t
finished yet, the unparceled object will end up in a stale
state. The unparceled object, being a different instance
than the source object,

assumes that ongoing operations at the moment


of parceling never took place

will not update its internal state when the


operations triggered by the source object

https://docs.parseplatform.org/android/guide/#users 17/171
7/26/22, 5:13 PM Android Developers Guide | Parse

The unfortunate consequence is that keys that were


dirty before saving will still be marked as dirty for the
unparceled object. This means, for instance, that any
future call to saveInBackground() will send these dirty
operations to the server again. This can lead to
inconsistencies for operations like increment , since it
might be performed twice.

PARCELING PARSEOBJECT SUBCLASSES

By default, ParseObject implementation parcels


everything that is needed. If your subclasses have
stateful information that you would like to keep when
parceling, you can simply override
onSaveInstanceState(Bundle) and
onRestoreInstanceState(Bundle) :

// Armor.java 📋
@ParseClassName("Armor")
public class Armor extends ParseObject {
private int member;

@Override
protected void onSaveInstanceState(Bundle
outState) {
outState.putInt("member", member);
}

@Override
protected void onRestoreInstanceState(Bundle
savedInstanceState) {
member =
savedInstanceState.getInt("member");
}
}

Want to contribute to this doc? Edit this section.

Queries
We’ve already seen how a ParseQuery with
getInBackground can retrieve a single ParseObject
from Parse. There are many other ways to retrieve data
with ParseQuery - you can retrieve many objects at
once, put conditions on the objects you wish to
retrieve, cache queries automatically to avoid writing
that code yourself, and more.

https://docs.parseplatform.org/android/guide/#users 18/171
7/26/22, 5:13 PM Android Developers Guide | Parse

Basic Queries
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
ParseObject 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<ParseObject> query = 📋
ParseQuery.getQuery("GameScore");
query.whereEqualTo("playerName", "Dan
Stemkoski");
query.findInBackground(new
FindCallback<ParseObject>() {
public void done(List<ParseObject>
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.

Query Constraints
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
https://docs.parseplatform.org/android/guide/#users 19/171
7/26/22, 5:13 PM Android Developers Guide | Parse

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. In the old Parse
hosted backend, the maximum limit was 1,000, but
Parse Server removed that constraint:

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
getFirstInBackground instead of using find .

ParseQuery<ParseObject> query = 📋
ParseQuery.getQuery("GameScore");
query.whereEqualTo("playerEmail",
"[email protected]");
query.getFirstInBackground(new
GetCallback<ParseObject>() {
public void done(ParseObject 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 . In the old
Parse hosted backend, the maximum skip value was
10,000, but Parse Server removed that constraint. 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
https://docs.parseplatform.org/android/guide/#users 20/171
7/26/22, 5:13 PM Android Developers Guide | Parse
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 whereNotContainedIn ,
providing an array of acceptable values. For example, if
you want to retrieve scores from players besides those
in a list:

https://docs.parseplatform.org/android/guide/#users 21/171
7/26/22, 5:14 PM Android Developers Guide | Parse

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<ParseObject> 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("homet
https://docs.parseplatform.org/android/guide/#users 22/171
7/26/22, 5:14 PM Android Developers Guide | Parse
"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
}
});

To filter rows based on objectId ’s from pointers in a


second table, you can use dot notation:

ParseQuery<ParseObject> chartersOfTypeX = 📋
ParseQuery.getQuery("Charter");
charterOfTypeX.equalTo('type', 'x');

ParseQuery<ParseObject> groupsWithoutCharterX =
ParseQuery.getQuery("Group");
groupsWithoutCharterX.doesNotMatchKeyInQuery("obje
"belongsTo.objectId", chartersOfTypeX);
groupsWithoutCharterX.findInBackground(new
FindCallback<ParseObject>() {
void done(List<ParseObject> results,
ParseException e) {
// results has the list of groups without
charter x
});

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<ParseObject> query = 📋
ParseQuery.getQuery("GameScore");
query.selectKeys(Arrays.asList("playerName",
"score"));;
List<ParseObject> results = query.find();

The remaining fields can be fetched later by calling one


of the fetchIfNeeded variants on the returned objects:

ParseObject object = results.get(0); 📋


object.fetchIfNeededInBackground(new
GetCallback<ParseObject>() {
public void done(ParseObject object,
ParseException e) {
// all fields of the object will now be
available here.
}
});

https://docs.parseplatform.org/android/guide/#users 23/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Queries on Array Values


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);

Queries on String Values


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<ParseObject> query =
ParseQuery.getQuery("BarbecueSauce");
query.whereStartsWith("name", "Big Daddy's");

The above example will match any BarbecueSauce


objects where the value in the “name” String key starts
with “Big Daddy’s”. For example, both “Big Daddy’s” and
“Big Daddy’s BBQ” will match, but “big daddy’s” or
“BBQ Sauce: Big Daddy’s” will not.

Queries that have regular expression constraints are


very expensive. Refer to the Performance Guide for
more details.

FULL TEXT SEARCH

https://docs.parseplatform.org/android/guide/#users 24/171
7/26/22, 5:14 PM Android Developers Guide | Parse

You can use whereFullText for efficient search


capabilities. Text indexes are automatically created for
you. Your strings are turned into tokens for fast
searching.

Note: Full Text Search can be resource intensive.


Ensure the cost of using indexes is worth the
benefit, see storage requirements &
performance costs of text indexes..

Requires Parse Server 2.5.0+

// Finds barbecue sauces that start with 'Big 📋


Daddy's'.
ParseQuery<ParseObject> query =
ParseQuery.getQuery("BarbecueSauce");
query.whereFullText("name", "Big Daddy's");

The above example will match any BarbecueSauce


objects where the value in the “name” String key
contains “bbq”. For example, both “Big Daddy’s BBQ”,
“Big Daddy’s bbq” and “Big BBQ Daddy” will match.

// You can sort by weight / rank. 📋


orderByAscending() and selectKeys()
ParseQuery<ParseObject> query =
ParseQuery.getQuery("BarbecueSauce");
query.whereFullText("name", "Big Daddy's");
query.orderByAscending("$score");
query.selectKeys(Arrays.asList("$score"));
// results will contain $score,
results[0].getInt("$score");
List<ParseObject> results = query.find();

For Case or Diacritic Sensitive search, please use the


REST API.

Relational Queries
There are several ways to issue queries for relational
data. If you want to retrieve objects where a field
matches a particular ParseObject , 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 :

https://docs.parseplatform.org/android/guide/#users 25/171
7/26/22, 5:14 PM Android Developers Guide | Parse

// Assume ParseObject myPost was previously 📋


created.
ParseQuery<ParseObject> query =
ParseQuery.getQuery("Comment");
query.whereEqualTo("post", myPost);

query.findInBackground(new
FindCallback<ParseObject>() {
public void done(List<ParseObject>
commentList, ParseException e) {
// commentList now has the comments for
myPost
}
});

If you want to retrieve objects where a field contains a


ParseObject that matches a different query, you can
use whereMatchesQuery . In order to find comments for
posts containing images, you can do:

ParseQuery<ParseObject> innerQuery = 📋
ParseQuery.getQuery("Post");
innerQuery.whereExists("image");
ParseQuery<ParseObject> query =
ParseQuery.getQuery("Comment");
query.whereMatchesQuery("post", innerQuery);
query.findInBackground(new
FindCallback<ParseObject>() {
public void done(List<ParseObject>
commentList, ParseException e) {
// comments now contains the comments for
posts with images.
}
});

If you want to retrieve objects where a field contains a


ParseObject that does not match a different query,
you can use whereDoesNotMatchQuery . In order to find
comments for posts without images, you can do:

ParseQuery<ParseObject> innerQuery = 📋
ParseQuery.getQuery("Post");
innerQuery.whereExists("image");
ParseQuery<ParseObject> query =
ParseQuery.getQuery("Comment");
query.whereDoesNotMatchQuery("post",
innerQuery);
query.findInBackground(new
FindCallback<ParseObject>() {
public void done(List<ParseObject>
commentList, ParseException e) {
// comments now contains the comments for
posts without images.
}
});

https://docs.parseplatform.org/android/guide/#users 26/171
7/26/22, 5:14 PM Android Developers Guide | Parse

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<ParseObject> 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<ParseObject>() {
public void done(List<ParseObject>
commentList, ParseException e) {
// commentList now contains the last ten
comments, and the "post"
// field has been populated. For example:
for (ParseObject comment : commentList) {
// This does not require a network
access.
ParseObject 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() .

Using the Local Datastore


If you have enabled the local datastore by calling
Parse.enableLocalDatastore() before your call to
Parse.initialize() , then you can also query against

https://docs.parseplatform.org/android/guide/#users 27/171
7/26/22, 5:14 PM Android Developers Guide | Parse

the objects stored locally on the device. To do this, call


the fromLocalDatastore method on the query.

query.fromLocalDatastore(); 📋
query.findInBackground(new
FindCallback<ParseObject>() {
public void done(final List<ParseObject>
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.

Caching Queries
WITH LOCAL DATASTORE ENABLED

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<ParseObject>() {
public void done(final List<ParseObject>
scoreList, ParseException e) {
if (e != null) {
// There was an error or the network
wasn't available.
return;
}

https://docs.parseplatform.org/android/guide/#users 28/171
7/26/22, 5:14 PM Android Developers Guide | Parse
// Release any objects previously pinned
for this query.

ParseObject.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.

ParseObject.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. ParseQuery
lets you choose whether to query the network
( fromNetwork ) or the local datastore
( fromLocalDatastore or fromPin(label) to query
just a subset). It is also possible to chain both requests,
or execute them in parallel.

For instance, to query the cache first and then the


network,

final ParseQuery query = 📋


ParseQuery.getQuery("GameScore");
query.fromLocalDatastore().findInBackground().cont
-> {
// Update UI with results from Local
Datastore ...
ParseException error = task.getError();
if(error == null){
List<ParseObject> gameScore =
task.getResult();
for(ParseObject game : gameScore){
//...
}
}
// Now query the network:
return
query.fromNetwork().findInBackground();
}, Task.UI_EXECUTOR).continueWithTask((task) ->
{
// Update UI with results from Network ...
ParseException error = task.getError();
if(error == null){
List<ParseObject> gameScore =
task.getResult();
for(ParseObject game : gameScore){
//...
}
}

https://docs.parseplatform.org/android/guide/#users 29/171
7/26/22, 5:14 PM Android Developers Guide | Parse
return task;
}, Task.UI_EXECUTOR);

Or you might want to query the cache, and if that fails,


fire a network call:

final ParseQuery query = 📋


ParseQuery.getQuery("GameScore");
query.fromLocalDatastore().findInBackground().cont
-> {
Exception error = task.getError();
if (error instanceof ParseException &&
((ParseException) error).getCode() ==
ParseException.CACHE_MISS) {
// No results from cache. Let's query the
network.
ParseException error = task.getError();
if(error == null){
List<ParseObject> gameScore =
task.getResult();
for(ParseObject game : gameScore){
//...
}
}
return
query.fromNetwork().findInBackground();
}
return task;
}).continueWithTask((task) -> {
// Update UI with results ...
return task;
}, Task.UI_EXECUTOR);

WITHOUT LOCAL DATASTORE

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.NETWOR

query.findInBackground(new
FindCallback<ParseObject>() {
public void done(List<ParseObject> 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.
}

https://docs.parseplatform.org/android/guide/#users 30/171
7/26/22, 5:14 PM Android Developers Guide | Parse
}
});

Parse provides several different cache policies:

IGNORE_CACHE : The query does not load from the


cache or save results to the cache. IGNORE_CACHE
is the default cache policy.

CACHE_ONLY : The query only loads from the


cache, ignoring the network. If there are no
cached results, that causes a ParseException .

NETWORK_ONLY : The query does not load from the


cache, but it will save results to the cache.

CACHE_ELSE_NETWORK : The query first tries to


load from the cache, but if that fails, it loads
results from the network. If neither cache nor
network succeed, there is a ParseException .

NETWORK_ELSE_CACHE : The query first tries to


load from the network, but if that fails, it loads
results from the cache. If neither network nor
cache succeed, there is a ParseException .

CACHE_THEN_NETWORK : The query first loads from


the cache, then loads from the network. In this
case, the 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:

Check to see if there is a cached result for the


query with:

📋
boolean isInCache = query.hasCachedResult();

Remove any cached results for a query with:

query.clearCachedResult(); 📋

https://docs.parseplatform.org/android/guide/#users 31/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Remove cached results for all queries with:

ParseQuery.clearAllCachedResults(); 📋

Query caching also works with ParseQuery helpers


including getFirst() and getInBackground() .

Counting Objects
Note: In the old Parse hosted backend, count queries
were rate limited to a maximum of 160 requests per
minute. They also returned inaccurate results for
classes with more than 1,000 objects. But, Parse
Server has removed both constraints and can count
objects well above 1,000.

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<ParseObject> 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.

Compound Queries
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.

https://docs.parseplatform.org/android/guide/#users 32/171
7/26/22, 5:14 PM Android Developers Guide | Parse

For instance if you want to find players who either have


a lot of wins or a few wins, you can do:

ParseQuery<ParseObject> lotsOfWins = 📋
ParseQuery.getQuery("Player");
lotsOfWins.whereGreaterThan(150);

ParseQuery<ParseObject> fewWins =
ParseQuery.getQuery("Player");
fewWins.whereLessThan(5);

List<ParseQuery<ParseObject>> queries = new


ArrayList<ParseQuery<ParseObject>>();
queries.add(lotsOfWins);
queries.add(fewWins);

ParseQuery<ParseObject> mainQuery =
ParseQuery.or(queries);
mainQuery.findInBackground(new
FindCallback<ParseObject>() {
public void done(List<ParseObject> 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 GeoPoint or


non-filtering constraints (e.g. whereNear ,
withinGeoBox , setLimit , skip , orderBy... ,
include ) in the subqueries of the compound query.

Want to contribute to this doc? Edit this section.

Users
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.

https://docs.parseplatform.org/android/guide/#users 33/171
7/26/22, 5:14 PM Android Developers Guide | Parse

ParseUser is a subclass of the ParseObject , and has


all the same features, such as flexible schema,
automatic persistence, and a key value interface. All the
methods that are on ParseObject also exist in
ParseUser . The difference is that ParseUser has some
special additions specific to user accounts.

ParseUser Properties
ParseUser has several properties that set it apart from
ParseObject :

username: The username for the user (required).

password: The password for the user (required


on signup).

email: The email address for the user (optional).

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.

Signing Up
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 protected]");

// other fields can be set just like with


ParseObject
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
}
}
});

https://docs.parseplatform.org/android/guide/#users 34/171
7/26/22, 5:14 PM Android Developers Guide | Parse

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 using bcrypt.
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.

Logging In
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
https://docs.parseplatform.org/android/guide/#users 35/171
7/26/22, 5:14 PM Android Developers Guide | Parse
ParseException to see what happened.
}
}
});

Verifying Emails
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:

1 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.

2 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 .

3 missing - the ParseUser was created when email


verification was off or the ParseUser does not
have an email .

Current User
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
}

https://docs.parseplatform.org/android/guide/#users 36/171
7/26/22, 5:14 PM Android Developers Guide | Parse

You can clear the current user by logging them out:

ParseUser.logOut(); 📋
ParseUser currentUser =
ParseUser.getCurrentUser(); // this will now be
null

Anonymous Users
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.getCurrent
{
enableSignUpButton();
} else {
https://docs.parseplatform.org/android/guide/#users 37/171
7/26/22, 5:14 PM Android Developers Guide | Parse
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();

Setting the Current User


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.
}
}
});

Security For User Objects


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,
https://docs.parseplatform.org/android/guide/#users 38/171
7/26/22, 5:14 PM Android Developers Guide | Parse

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.

Security for Other Objects


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.

https://docs.parseplatform.org/android/guide/#users 39/171
7/26/22, 5:14 PM Android Developers Guide | Parse

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:

ParseObject privateNote = new 📋


ParseObject("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:

ParseObject groupMessage = new 📋


ParseObject("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

https://docs.parseplatform.org/android/guide/#users 40/171
7/26/22, 5:14 PM Android Developers Guide | Parse

create a post that can only be edited by its author, but


can be read by anyone:

ParseObject publicPost = new 📋


ParseObject("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); 📋

https://docs.parseplatform.org/android/guide/#users 41/171
7/26/22, 5:14 PM Android Developers Guide | Parse

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.

Resetting Passwords
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:

📋
ParseUser.requestPasswordResetInBackground("myemai
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:

https://docs.parseplatform.org/android/guide/#users 42/171
7/26/22, 5:14 PM Android Developers Guide | Parse

User requests that their password be reset by


1
typing in their email.

2 Parse sends an email to their address, with a


special password reset link.

3 User clicks on the reset link, and is directed to a


special Parse page that will allow them type in a
new password.

4 User types in a new password. Their password


has now been reset to a value they specify.

Note that the messaging in this flow will reference your


app by the name that you specified when you created
this app on Parse.

Querying
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
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
ParseObject post = new ParseObject("Post");
post.put("title", "My New Post");
post.put("body", "This is some great
https://docs.parseplatform.org/android/guide/#users 43/171
7/26/22, 5:14 PM Android Developers Guide | Parse
content.");
post.put("user", user);
post.saveInBackground();

// Find all posts by the current user


ParseQuery<ParseObject> query =
ParseQuery.getQuery("Post");
query.whereEqualTo("user", user);
query.findInBackground(new
FindCallback<ParseObject>() { ... });

Facebook Users
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.

Note: Parse is compatible with both Facebook SDK 3.x


and 4.x for Android. These instructions are for
Facebook SDK 4.x.

SETTING UP FACEBOOK

To start using Facebook with Parse, you need to:

1 Set up a Facebook app, if you haven’t already.

2 Add your application’s Facebook Application ID


on your Parse application’s settings page.

3 Follow Facebook’s instructions for getting


started with the Facebook SDK to create an app
linked to the Facebook SDK. Once you get to
Step 6, stop after linking the Facebook SDK
project and configuring the Facebook app ID. You
can use our guide to attach your Parse users to
Facebook accounts when logging in.

4 Add 'com.github.parse-
community:ParseFacebookUtils-
Android:latest.version.here' to your Gradle
dependencies (X.X.X should be JitPack 4.0.0 )

5 Add the following where you initialize the Parse


SDK in your Application.onCreate() :
https://docs.parseplatform.org/android/guide/#users 44/171
7/26/22, 5:14 PM Android Developers Guide | Parse

ParseFacebookUtils.initialize(context); 📋

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” (SSO), and requires you to
override onActivityResult() in your calling
Activity :

@Override 📋
protected void onActivityResult(int
requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode,
resultCode, data);

ParseFacebookUtils.onActivityResult(requestCode,
resultCode, data);
}

If your Activity is already using onActivityResult() ,


you can avoid requestCode collisions by specifying
your own request code offset when initializing
ParseFacebookUtils.initialize(context,
callbackRequestCodeOffset) . 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.

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 .

It is up to you to record any data that you need from the


Facebook user after they authenticate. To accomplish
this, you'll need to do a graph query using the Facebook
SDK.

https://docs.parseplatform.org/android/guide/#users 45/171
7/26/22, 5:14 PM Android Developers Guide | Parse
LOGIN & SIGNUP

ParseFacebookUtils provides a way to allow your


ParseUser s to log in or sign up through Facebook. This
is generally accomplished using the
logInWithReadPermissionsInBackground(String,
Collection<String>) method:

📋
ParseFacebookUtils.logInWithReadPermissionsInBackg
permissions, 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:

1 The user is shown the Facebook login dialog or a


prompt generated by the Facebook app.

2 The user authenticates via Facebook, and your


app receives a callback.

3 Our SDK receives the user’s Facebook access


data and saves it to a ParseUser . If no
ParseUser exists with the same Facebook ID,
then a new ParseUser is created.

4 Your LogInCallback is called with the user.

5 The current user reference will be updated to this


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
logInWithReadPermissionsInBackground() from
within the Activity ) as we have done above.

ParseUser integration doesn’t require any permissions


to work out of the box (i.e. null is perfectly
acceptable). When logging in, you can only use read
https://docs.parseplatform.org/android/guide/#users 46/171
7/26/22, 5:14 PM Android Developers Guide | Parse

permissions. See our documentation below about


requesting additional permissions (read or publish).
Read more about permissions on Facebook’s
developer guide.

FACEBOOK LINKING

If you want to associate an existing ParseUser to a


Facebook account, you can link it like so:

if (!ParseFacebookUtils.isLinked(user)) { 📋
ParseFacebookUtils.linkWithReadPermissionsInBackgr
this, permissions, 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.");
}
}
});

REQUESTING PERMISSIONS

As of v3.0 of the Facebook SDK, read and publish


permissions must be requested separately. To request
additional permissions, you may call
ParseFacebookUtils.linkWithReadPermissionsInBack
ground() or

https://docs.parseplatform.org/android/guide/#users 47/171
7/26/22, 5:14 PM Android Developers Guide | Parse

ParseFacebookUtils.linkWithPublishPermissionsInB
ackground() . For more information about requesting
new permissions, please see Facebook’s API
documentation for these functions.

After successfully retrieving new permissions, please


call
ParseFacebookUtilities.linkInBackground(ParseUse
r, AccessToken) , 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.

FACEBOOK SDK AND PARSE

The Facebook Android SDK provides a number of


helper classes for interacting with Facebook’s API.
Generally, you will use the GraphRequest class to
interact with Facebook on behalf of your logged-in user.
You can read more about the Facebook SDK here.

To access the user’s AccessToken you can simply call


AccessToken.getCurrentAccessToken() to access the
AccessToken instance, which can then be passed to
GraphRequest s.

Twitter Users
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.

SETTING UP TWITTER

To start using Twitter with Parse, you need to:

1 Set up a Twitter app, if you haven’t already.

2 Add your application’s Twitter consumer key on


your Parse application’s settings page.

3 When asked to specify a “Callback URL” for your


Twitter app, please insert twittersdk:// . This is

https://docs.parseplatform.org/android/guide/#users 48/171
7/26/22, 5:14 PM Android Developers Guide | Parse

necessary in order to enable authentication


through Twitter.

4 Add 'com.github.parse-
community:ParseTwitterUtils-
Android:latest.version.here' to your Gradle
dependencies.

5 Add the following where you initialize the Parse


SDK in your 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 .

LOGIN & SIGNUP

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:

1 The user is shown the Twitter login dialog.

2 The user authenticates via Twitter, and your app


receives a callback.

https://docs.parseplatform.org/android/guide/#users 49/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Our SDK receives the Twitter data and saves it to


3
a ParseUser . If it’s a new user based on the
Twitter handle, then that user is created.

4 Your 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.

TWITTER LINKING

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.");
}
}
});

TWITTER API CALLS

https://docs.parseplatform.org/android/guide/#users 50/171
7/26/22, 5:14 PM Android Developers Guide | Parse

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
using OkHttp, you can use the Twitter singleton
provided by ParseTwitterUtils :

OkHttpClient client = new OkHttpClient(); 📋


Request request = new Request.Builder()

.url("https://api.twitter.com/1.1/account/verify_c

.build();

OkHttpOAuthConsumer consumer = new


OkHttpOAuthConsumer(Twitter.getConsumerKey(),
Twitter.getConsumerSecret());
Request signedRequest = (Request)
consumer.sign(request).unwrap();
// or enqueue
Response response =
client.newCall(signedRequest).execute();

Want to contribute to this doc? Edit this section.

Sessions
Sessions represent an instance of a user logged into a
device. Sessions are automatically created when users
log in or sign up. They are automatically deleted when
users log out. There is one distinct Session object for
each user-installation pair; if a user issues a login
request from a device they’re already logged into, that
user’s previous Session object for that Installation is
automatically deleted. Session objects are stored on
Parse in the Session class, and you can view them on
the Parse Dashboard Data Browser. We provide a set of
APIs to manage Session objects in your app.

Session is a subclass of a Parse Object , so you can


query, update, and delete sessions in the same way that
you manipulate normal objects on Parse. Because Parse
Server automatically creates sessions when you log in
or sign up users, you should not manually create
Session objects unless you are building an IoT app (e.g.
Arduino or Embedded C). Deleting a Session will log

https://docs.parseplatform.org/android/guide/#users 51/171
7/26/22, 5:14 PM Android Developers Guide | Parse

the user out of the device that is currently using this


session’s token.

Unlike other Parse objects, the Session class does not


have Cloud Code triggers. So you cannot register a
beforeSave or afterSave handler for the Session
class.

Session Properties
The Session object has these special fields:

sessionToken (readonly): String token for


authentication on Parse API requests. In the
response of Session queries, only your current
Session object will contain a session token.

user : (readonly) Pointer to the User object that


this session is for.

createdWith (readonly): Information about how


this session was created (e.g. { "action":
"login", "authProvider": "password"} ).

action could have values: login , signup ,


create , or upgrade . The create action is
when the developer manually creates the
session by saving a Session object. The
upgrade action is when the user is
upgraded to revocable session from a
legacy session token.

authProvider could have values:


password , anonymous , facebook , or
twitter .

expiresAt (readonly): Approximate UTC date


when this Session object will be automatically
deleted. You can configure session expiration
settings (either 1-year inactivity expiration or no
expiration) in your app’s Parse Dashboard
settings page.

installationId (can be set only once): String


referring to the Installation where the session
is logged in from. For Parse SDKs, this field will be
automatically set when users log in or sign up. All
special fields except installationId can only be
set automatically by Parse Server. You can add
custom fields onto Session objects, but please

https://docs.parseplatform.org/android/guide/#users 52/171
7/26/22, 5:14 PM Android Developers Guide | Parse

keep in mind that any logged-in device (with


session token) can read other sessions that
belong to the same user (unless you disable
Class-Level Permissions, see below).

Handling Invalid Session Token Error


With revocable sessions, your current session token
could become invalid if its corresponding Session
object is deleted from your Parse Server. This could
happen if you implement a Session Manager UI that lets
users log out of other devices, or if you manually delete
the session via Cloud Code, REST API, or Data Browser.
Sessions could also be deleted due to automatic
expiration (if configured in app settings). When a
device’s session token no longer corresponds to a
Session object on your Parse Server, all API requests
from that device will fail with “Error 209: invalid session
token”.

To handle this error, we recommend writing a global


utility function that is called by all of your Parse request
error callbacks. You can then handle the “invalid session
token” error in this global function. You should prompt
the user to login again so that they can obtain a new
session token. This code could look like this:

public class ParseErrorHandler { 📋


public static void
handleParseError(ParseException e) {
switch (e.getCode()) {
case INVALID_SESSION_TOKEN:
handleInvalidSessionToken()
break;

... // Other Parse API errors that you


want to explicitly handle
}
}

private static void


handleInvalidSessionToken() {
//--------------------------------------
// Option 1: Show a message asking the user
to log out and log back in.
//--------------------------------------
// If the user needs to finish what they
were doing, they have the opportunity to do so.
//
// new AlertDialog.Builder(getActivity())
// .setMessage("Session is no longer
valid, please log out and log in again.")
//
.setCancelable(false).setPositiveButton("OK",
...).create().show();

https://docs.parseplatform.org/android/guide/#users 53/171
7/26/22, 5:14 PM Android Developers Guide | Parse
//--------------------------------------
// Option #2: Show login screen so user can
re-authenticate.
//--------------------------------------
// You may want this if the logout button
could be inaccessible in the UI.
//
// startActivityForResult(new
ParseLoginBuilder(getActivity()).build(), 0);
}
}

// In all API requests, call the global error


handler, e.g.
query.findInBackground(new
FindCallback<ParseObject>() {
public void done(List<ParseObject> results,
ParseException e) {
if (e == null) {
// Query successful, continue other app
logic
} else {
// Query failed
ParseErrorHandler.handleParseError(e);
}
}
});

Session Security
Session objects can only be accessed by the user
specified in the user field. All Session objects have an
ACL that is read and write by that user only. You cannot
change this ACL. This means querying for sessions will
only return objects that match the current logged-in
user.

When you log in a user via a User login method, Parse


will automatically create a new unrestricted Session
object in your Parse Server. Same for signups and
Facebook/Twitter logins.

You can configure Class-Level Permissions (CLPs) for


the Session class just like other classes on Parse. CLPs
restrict reading/writing of sessions via the Session
API, but do not restrict Parse Server’s automatic
session creation/deletion when users log in, sign up,
and log out. We recommend that you disable all CLPs
not needed by your app. Here are some common use
cases for Session CLPs:

Find, Delete — Useful for building a UI screen


that allows users to see their active session on all
devices, and log out of sessions on other devices.

https://docs.parseplatform.org/android/guide/#users 54/171
7/26/22, 5:14 PM Android Developers Guide | Parse

If your app does not have this feature, you should


disable these permissions.

Create — Useful for apps that provision user


sessions for other devices from the phone app.
You should disable this permission when building
apps for mobile and web. For IoT apps, you should
check whether your IoT device actually needs to
access user-specific data. If not, then your IoT
device does not need a user session, and you
should disable this permission.

Get, Update, Add Field — Unless you need these


operations, you should disable these permissions.

Want to contribute to this doc? Edit this section.

Roles
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.

https://docs.parseplatform.org/android/guide/#users 55/171
7/26/22, 5:14 PM Android Developers Guide | Parse

ParseRole is a subclass of ParseObject , 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 ParseObject also exist on ParseRole . The
difference is that ParseRole has some additions
specific to management of roles.

ParseRole Properties
ParseRole has several properties that set it apart from
ParseObject :

name: The name for the role. This value is


required, and can only be set once as a role is
being created. The name must consist of
alphanumeric characters, spaces, -, or _. This
name will be used to identify the Role without
needing its objectId.

users: A relation to the set of users that will


inherit permissions granted to the containing
role.

roles: A relation to the set of roles whose users


and roles will inherit permissions granted to the
containing role.

Security for Role Objects


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();

https://docs.parseplatform.org/android/guide/#users 56/171
7/26/22, 5:14 PM Android Developers Guide | Parse

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.

Role Based Security for Other Objects


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
ParseObject 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 */;
ParseObject wallPost = new
ParseObject("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:

ParseObject wallPost = new 📋


ParseObject("WallPost");
ParseACL postACL = new ParseACL();
postACL.setRoleWriteAccess("Moderators", true);
wallPost.setACL(postACL);
wallPost.save();

https://docs.parseplatform.org/android/guide/#users 57/171
7/26/22, 5:14 PM Android Developers Guide | Parse

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);

Role Hierarchy
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();

Want to contribute to this doc? Edit this section.

https://docs.parseplatform.org/android/guide/#users 58/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Files
The ParseFile
ParseFile lets you store application files in the cloud
that would otherwise be too large or cumbersome to fit
into a regular ParseObject . The most common use
case is storing images but you can also use it for
documents, videos, music, and any other binary data.

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:

You don’t need to worry about filename


collisions. Each upload gets a unique identifier so
there’s no problem with uploading multiple files
named resume.txt .

It’s important that you give a name to the file that


has a file extension. This lets Parse figure out the
file type and handle it accordingly. So, if you’re
storing PNG images, make sure your filename
ends with .png .

Next you’ll want to save the file up to the cloud. As with


ParseObject , there are many variants of the save
method you can use depending on what sort of callback
and error handling suits you.

file.saveInBackground(); 📋

https://docs.parseplatform.org/android/guide/#users 59/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Finally, after the save completes, you can associate a


ParseFile onto a ParseObject just like any other
piece of data:

ParseObject jobApplication = new 📋


ParseObject("JobApplication");
jobApplication.put("applicantName", "Joe
Smith");
jobApplication.put("applicantResumeFile",
file);
jobApplication.saveInBackground();

Retrieving it back involves calling one of the getData


variants on the ParseObject . Here we retrieve the
resume file off another JobApplication object:

ParseFile applicantResume = 📋
(ParseFile)anotherApplication.get("applicantResume

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 ParseObject , you will most likely want to


use the background version of getData .

Progress
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) {
https://docs.parseplatform.org/android/guide/#users 60/171
7/26/22, 5:14 PM Android Developers Guide | Parse
// 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.

Using Parcelable
As most public facing components of the SDK,
ParseFile implements the Parcelable interface. This
means you can retain a ParseFile during configuration
changes, or pass it to other components of the app
through Bundles . To achieve this, depending on the
context, use either
Parcel#writeParcelable(Parcelable, int) or
Bundle#putParcelable(String, Parcelable) . For
instance, in an Activity,

private ParseFile file; 📋


@Override
protected void onSaveInstanceState(Bundle
outState) {
super.onSaveInstanceState(outState);
if (!file.isDirty()) {
outState.putParcelable("file", file);
}
}

@Override
protected void onCreate(@Nullable Bundle
savedInstanceState) {
if (savedInstanceState != null) {
file = (ParseFile)
savedInstanceState.getParcelable("file");
}
}

https://docs.parseplatform.org/android/guide/#users 61/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Note, however, that files can’t be parceled if they are


not saved on the server. If you try to do so, an exception
will be thrown. If you are not sure if your file has been
saved, please check for !isDirty() to be true before
writing it to a parcel.

Want to contribute to this doc? Edit this section.

GeoPoints
Parse allows you to associate real-world latitude and
longitude coordinates with an object. Adding a
ParseGeoPoint to a ParseObject 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.

ParseGeoPoint
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); 📋

To retrieve a ParseGeoPoint from an object.

placeObject.getParseGeoPoint("location"); 📋

ParsePolygon

https://docs.parseplatform.org/android/guide/#users 62/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Parse allows you to associate polygon coordinates with


an object. Adding a ParsePolygon to a ParseObject
allows queries to determine whether a ParseGeoPoint
is within a ParsePolygon or if a ParsePolygon
contains a ParseGeoPoint .

ParsePolygon must contain at least three


coordinates.

For example, to create a polygon with coordinates (0,


0), (0, 1), (1, 1), (1, 0).

List<ParseGeoPoint> points = new 📋


ArrayList<ParseGeoPoint>();
points.add(new ParseGeoPoint(0,0));
points.add(new ParseGeoPoint(0,1));
points.add(new ParseGeoPoint(1,1));
points.add(new ParseGeoPoint(1,0));

ParsePolygon polygon = new


ParsePolygon(points);

This point is then stored in the object as a regular field.

placeObject.put("bounds", polygon); 📋

To retrieve a ParsePolygon from an object.

placeObject.getParsePolygon("bounds"); 📋

Geo Queries
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<ParseObject> query =
ParseQuery.getQuery("PlaceObject");
query.whereNear("location", userLocation);
query.setLimit(10);
query.findInBackground(new
FindCallback<ParseObject>() { ... });
https://docs.parseplatform.org/android/guide/#users 63/171
7/26/22, 5:14 PM Android Developers Guide | Parse

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<ParseObject> query =
ParseQuery.getQuery("PizzaPlaceObject");
query.whereWithinGeoBox("location",
southwestOfSF, northeastOfSF);
query.findInBackground(new
FindCallback<ParseObject>() { ... });

You can query for whether an object lies within or on a


polygon of Parse.GeoPoint .

ParseGeoPoint point = new ParseGeoPoint(0.5, 📋


0.5);
ParseQuery<ParseObject> query =
ParseQuery.getQuery("PlaceObject");
query.wherePolygonContains("location", point);

You can also query for whether an object


Parse.Polygon contains a Parse.GeoPoint .

ParseGeoPoint point = new ParseGeoPoint(0.5, 📋


0.5);
ParseQuery<ParseObject> query =
ParseQuery.getQuery("PlaceObject");
query.wherePolygonContains("location", point);

https://docs.parseplatform.org/android/guide/#users 64/171
7/26/22, 5:14 PM Android Developers Guide | Parse

To efficiently find if a ParsePolygon contains a


ParseGeoPoint without querying use containsPoint .

List<ParseGeoPoint> points = new 📋


ArrayList<ParseGeoPoint>();
points.add(new ParseGeoPoint(0, 0));
points.add(new ParseGeoPoint(0, 1));
points.add(new ParseGeoPoint(1, 1));
points.add(new ParseGeoPoint(1, 0));

ParseGeoPoint inside = new ParseGeoPoint(0.5,


0.5);
ParseGeoPoint outside = new ParseGeoPoint(10,
10);

ParsePolygon polygon = new


ParsePolygon(points);

// Returns True
polygon.containsPoint(inside);

// Returns False
polygon.containsPoint(outside);

Utilizing Parcelable
As most public facing components of the SDK,
ParseGeoPoint and ParsePolygon implements the
Parcelable interface. This means you can retain a
ParseGeoPoint and ParsePolygon during
configuration changes, or pass it to other components
of the app through Bundles . To achieve this, depending
on the context, use either
Parcel#writeParcelable(Parcelable, int) or
Bundle#putParcelable(String, Parcelable) . For
instance, in an Activity,

private ParseGeoPoint point; 📋


@Override
protected void onSaveInstanceState(Bundle
outState) {
super.onSaveInstanceState(outState);
outState.putParcelable("point", point);
}

@Override
protected void onCreate(@Nullable Bundle
savedInstanceState) {
if (savedInstanceState != null) {
point = (ParseGeoPoint)
savedInstanceState.getParcelable("point");
}
}

https://docs.parseplatform.org/android/guide/#users 65/171
7/26/22, 5:14 PM Android Developers Guide | Parse

private ParsePolygon polygon; 📋


@Override
protected void onSaveInstanceState(Bundle
outState) {
super.onSaveInstanceState(outState);
outState.putParcelable("polygon", polygon);
}

@Override
protected void onCreate(@Nullable Bundle
savedInstanceState) {
if (savedInstanceState != null) {
polygon = (ParsePolygon)
savedInstanceState.getParcelable("polygon");
}
}

Caveats
At the moment there are a couple of things to watch
out for:

1 Each ParseObject class may only have one key


with a ParseGeoPoint object.

2 Using the whereNear constraint will also limit


results to within 100 miles.

3 Points should not equal or exceed the extreme


ends of the ranges. Latitude should not be -90.0
or 90.0. Longitude should not be -180.0 or
180.0. Attempting to set latitude or longitude out
of bounds will cause an error.

Want to contribute to this doc? Edit this section.

Local Datastore
The Parse Android SDK provides a local datastore
which can be used to store and retrieve ParseObject 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 {


https://docs.parseplatform.org/android/guide/#users 66/171
7/26/22, 5:14 PM Android Developers Guide | Parse
@Override
public void onCreate() {
super.onCreate();

Parse.enableLocalDatastore(this);
Parse.initialize(this);
}
}

If you use a Parse.Configuration.Builder , enable it


there instead:

Parse.initialize(new 📋
Parse.Configuration.Builder(context)
.server(...)
.applicationId(...)
.enableLocalDataStore()
.build());

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
ParseObject . 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 ParseObject


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.getCurrentUser().saveEventually() ,
your app will always see the changes that you have
made.

Pinning
You can store a ParseObject in the local datastore by
pinning it. Pinning a ParseObject is recursive, just like
saving, so any objects that are pointed to by the one

https://docs.parseplatform.org/android/guide/#users 67/171
7/26/22, 5:14 PM Android Developers Guide | Parse

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.

ParseObject gameScore = new 📋


ParseObject("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.

ParseObject.pinAllInBackground(listOfObjects);📋

Retrieving
Storing objects is great, but it’s only useful if you can
then get the objects back out later. To retrieve an
object from the local datastore, call the
fromLocalDatastore method to tell the ParseQuery
where to look for its results.

ParseQuery<ParseObject> query = 📋
ParseQuery.getQuery("GameScore");
query.fromLocalDatastore();

The only difference is that you won’t be able to access


any data protected by Role based ACLs due to the fact
that the Roles are stored on the server. To access this
data protected by Role based ACLs, you may need to
ignore ACLs when executing a Local Datastore query by
calling the ignoreAcls method on the query. Note that
after calling this, you cannot subsequently use the same
query to load from the network.

// If data is protected by Role based ACLs: 📋


query.ignoreAcls();

https://docs.parseplatform.org/android/guide/#users 68/171
7/26/22, 5:14 PM Android Developers Guide | Parse

You can then retrieve objects as normal.

query.getInBackground("xWMyZ4YE", new 📋
GetCallback<ParseObject>() {
public void done(ParseObject object,
ParseException e) {
if (e == null) {
// object will be your game score
} else {
// something went wrong
}
}
});

Querying the Local Datastore


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<ParseObject> query = 📋
ParseQuery.getQuery("GameScore");
query.whereEqualTo("playerName", "Joe Bob");
query.fromLocalDatastore();
// If data is protected by Role based ACLs:
query.ignoreAcls();
query.findInBackground(new
FindCallback<ParseObject>() {
public void done(List<ParseObject>
scoreList,
ParseException e) {
if (e == null) {
Log.d("score", "Retrieved " +
scoreList.size());
} else {
Log.d("score", "Error: " +
e.getMessage());
}
}
});

Unpinning
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.

https://docs.parseplatform.org/android/guide/#users 69/171
7/26/22, 5:14 PM Android Developers Guide | Parse

gameScore.unpinInBackground(); 📋

There’s also a method to unpin several objects at once.

📋
ParseObject.unpinAllInBackground(listOfObjects);

Pinning with Labels


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. 📋


ParseObject.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.

ParseObject.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.

https://docs.parseplatform.org/android/guide/#users 70/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Caching Query Results


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<ParseObject> query = 📋
ParseQuery.getQuery(“GameScore");
query.orderByDescending(“score”);

// Query for new results from the network.


query.findInBackground(new
FindCallback<ParseObject>() {
public void done(final List<ParseObject>
scores, ParseException e) {
// Remove the previously cached results.

ParseObject.unpinAllInBackground(“highScores”,
new DeleteCallback() {
public void done(ParseException e) {
// Cache the new results.

ParseObject.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<ParseObject> query = 📋
ParseQuery.getQuery(“GameScore");
query.orderByDescending(“score”);
query.fromLocalDatastore();

query.findInBackground(new
FindCallback<ParseObject>() {
public void done(List<ParseObject> scores,
ParseException e) {
// Yay! Cached scores!
}
});

Syncing Local Changes


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 ParseObject , it will be pinned until it can be saved.

https://docs.parseplatform.org/android/guide/#users 71/171
7/26/22, 5:14 PM Android Developers Guide | Parse

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<ParseObject> query = 📋
ParseQuery.getQuery(“GameScore");
query.fromPin(“MyChanges”);
query.findInBackground(new
FindCallback<ParseObject>() {
public void done(List<ParseObject> scores,
ParseException e) {
for (ParseObject score in scores) {
score.saveInBackground();
score.unpinInBackground(“MyChanges”);
}
}
});

Want to contribute to this doc? Edit this section.

Push Notifications
Push notifications are a great way to keep your users
engaged and informed about your app. You can reach
your entire user base quickly and effectively. This guide
will help you through the setup process and the general
usage of Parse to send push notifications.

If you haven’t installed the SDK yet, head over to the


Push QuickStart to get our SDK up and running.

Setting Up Push

https://docs.parseplatform.org/android/guide/#users 72/171
7/26/22, 5:14 PM Android Developers Guide | Parse

If you want to start using push, start by completing the


Android Push Notifications QuickStart Guide to learn
how to configure your app and send your first push
notification. Come back to this guide afterwards to
learn more about the push features offered by Parse.

The Parse library provides push notifications using


Firebase Cloud Messaging (FCM) if Google Play
Services are available. Learn more about Google Play
Services here.

Installations
Every Parse application installed on a device registered
for push notifications has an associated Installation
object. The Installation object is where you store all
the data needed to target push notifications. For
example, in a baseball app, you could store the teams a
user is interested in to send updates about their
performance. Saving the Installation object is also
required for tracking push-related app open events.

In Android, Installation objects are available through


the ParseInstallation class, a subclass of
ParseObject . It uses the same API for storing and
retrieving data. To access the current Installation
object from your Android app, use the
ParseInstallation.getCurrentInstallation()
method. The first time you save a ParseInstallation ,
Parse will add it to your Installation class and it will
be available for targeting push notifications.

// Save the current Installation to Parse. 📋


ParseInstallation.getCurrentInstallation().saveInB

While it is possible to modify a ParseInstallation just


like you would a ParseObject , there are several special
fields that help manage and target devices.

channels : An array of the channels to which a


device is currently subscribed.

https://docs.parseplatform.org/android/guide/#users 73/171
7/26/22, 5:14 PM Android Developers Guide | Parse

installationId : Unique Id for the device used


by Parse (readonly).

deviceType : The type of device, “ios”, “osx”,


“android”, “winrt”, “winphone”, “dotnet”, or
“embedded”. On Android devices, this field will be
set to “android” (readonly).

pushType : This field is reserved for directing


Parse to the push delivery network to be used. If
the device is registered to receive pushes via
FCM, this field will be marked “gcm”. If Google
Play Services is not available, it will be blank
(readonly).

deviceToken : The token used by FCM to keep


track of registration ID. On iOS devices, this is a
generated token (readonly).

appName : The display name of the client


application to which this installation belongs. This
value is synchronized every time a
ParseInstallation object is saved from the
device (readonly).

appVersion : The version string of the client


application to which this installation belongs. This
value is synchronized every time a
ParseInstallation object is saved from the
device (readonly).

parseVersion : The version of the Parse SDK


which this installation uses. This value is
synchronized every time a ParseInstallation
object is saved from the device (readonly).

timeZone : The current time zone where the


target device is located. This value is
synchronized every time a ParseInstallation
object is saved from the device (readonly).

localeIdentifier : The locale identifier of the


device in the format [language code]-[COUNTRY
CODE]. The language codes are two-letter
lowercase ISO language codes (such as “en”) as
defined by ISO 639-1. The country codes are two-
letter uppercase ISO country codes (such as
“US”) as defined by ISO 3166-1. This value is
synchronized every time a ParseInstallation
object is saved from the device (readonly).

badge : The current value of the icon badge for


iOS apps. Changes to this value on the server will
be used for future badge-increment push
notifications.

https://docs.parseplatform.org/android/guide/#users 74/171
7/26/22, 5:14 PM Android Developers Guide | Parse

channelUris : The Microsoft-generated push


URIs for Windows devices (readonly).

appIdentifier : A unique identifier for this


installation’s client application. This parameter is
not supported in Android (readonly).

The Parse Android SDK will avoid making unnecessary


requests. If a ParseInstallation is saved on the
device, a request to the Parse servers will only be made
if one of the ParseInstallation ’s fields has been
explicitly updated.

Sending Pushes
There are two ways to send push notifications using
Parse: channels and advanced targeting. Channels
offer a simple and easy to use model for sending
pushes, while advanced targeting offers a more
powerful and flexible model. Both are fully compatible
with each other and will be covered in this section.

Sending notifications is often done from the Parse


Dashboard push console, the REST API or from Cloud
Code. However, push notifications can also be triggered
by the existing client SDKs. If you decide to send
notifications from the client SDKs, you will need to set
Client Push Enabled in the Push Notifications settings
of your Parse app.

However, be sure you understand that enabling Client


Push can lead to a security vulnerability in your app. We
recommend that you enable Client Push for testing
purposes only, and move your push notification logic
into Cloud Code when your app is ready to go into
production.

You can view your past push notifications on the Parse


Dashboard push console for up to 30 days after
creating your push. For pushes scheduled in the future,
you can delete the push on the push console as long as
no sends have happened yet. After you send the push,
the push console shows push analytics graphs.

https://docs.parseplatform.org/android/guide/#users 75/171
7/26/22, 5:14 PM Android Developers Guide | Parse
USING CHANNELS

The simplest way to start sending notifications is using


channels. This allows you to use a publisher-subscriber
model for sending pushes. Devices start by subscribing
to one or more channels, and notifications can later be
sent to these subscribers. The channels subscribed to
by a given Installation are stored in the channels
field of the Installation object.

SUBSCRIBING TO CHANNELS

A channel is identified by a string that starts with a


letter and consists of alphanumeric characters,
underscores, and dashes. It doesn’t need to be explicitly
created before it can be used and each Installation
can subscribe to any number of channels at a time.

Subscribing to a channel can be done using a single


method call. For example, in a baseball score app, we
could do:

📋
// When users indicate they are Giants fans, we
subscribe them to that channel.
ParsePush.subscribeInBackground("Giants");

By default, the main activity for your app will be run


when a user responds to notifications.

Once subscribed to the “Giants” channel, your


Installation object should have an updated
channels field.

Unsubscribing from a channel is just as easy:

// When users indicate they are no longer 📋


Giants fans, we unsubscribe them.
ParsePush.unsubscribeInBackground("Giants");

You can also get the set of channels that the current
device is subscribed to using:

List<String> subscribedChannels = 📋
ParseInstallation.getCurrentInstallation().getList
https://docs.parseplatform.org/android/guide/#users 76/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Neither the subscribe method nor the unsubscribe


method blocks the thread it is called from. The
subscription information is cached on the device’s disk
if the network is inaccessible and transmitted to your
Parse Server as soon as the network is usable. This
means you don’t have to worry about threading or
callbacks while managing subscriptions.

SENDING PUSHES TO CHANNELS

In the Android SDK, the following code can be used to


alert all subscribers of the “Giants” channel that their
favorite team just scored. This will display a notification
center alert to iOS users and a system tray notification
to Android users.

ParsePush push = new ParsePush(); 📋


push.setChannel("Giants");
push.setMessage("The Giants just scored! It's
now 2-2 against the Mets.");
push.sendInBackground();

If you want to target multiple channels with a single


push notification, you can use a LinkedList of
channels.

LinkedList<String> channels = new 📋


LinkedList<String>();
channels.add("Giants");
channels.add("Mets");

ParsePush push = new ParsePush();


push.setChannels(channels); // Notice we use
setChannels not setChannel
push.setMessage("The Giants won against the
Mets 2-3.");
push.sendInBackground();

USING ADVANCED TARGETING

While channels are great for many applications,


sometimes you need more precision when targeting the
recipients of your pushes. Parse allows you to write a
query for any subset of your Installation objects
using the querying API and to send them a push.

https://docs.parseplatform.org/android/guide/#users 77/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Since ParseInstallation is a subclass of


ParseObject , you can save any data you want and even
create relationships between Installation objects
and your other objects. This allows you to send pushes
to a very customized and dynamic segment of your user
base.

SAVING INSTALLATION DATA

Storing data on a ParseInstallation object is just as


easy as storing any other data on Parse. In our Baseball
app, we could allow users to get pushes about game
results, scores and injury reports.

// Store app language and version 📋


ParseInstallation installation =
ParseInstallation.getCurrentInstallation();
installation.put("scores",true);
installation.put("gameResults",true);
installation.put("injuryReports",true);
installation.saveInBackground();

You can even create relationships between your


Installation objects and other classes saved on
Parse. To associate a ParseInstallation with a
particular user, for example, you can simply store the
current user on the ParseInstallation .

// Associate the device with a user 📋


ParseInstallation installation =
ParseInstallation.getCurrentInstallation();
installation.put("user",ParseUser.getCurrentUser()

installation.saveInBackground();

SENDING PUSHES TO QUERIES

Once you have your data stored on your


ParseInstallation objects, you can use a
ParseQuery to target a subset of these devices.
ParseInstallation queries work just like any other
Parse query, but we use the special static method
ParseInstallation.getQuery() to create it. We set
this query on our ParsePush object, before sending the
notification.

https://docs.parseplatform.org/android/guide/#users 78/171
7/26/22, 5:14 PM Android Developers Guide | Parse

// Create our Installation query 📋


ParseQuery pushQuery =
ParseInstallation.getQuery();
pushQuery.whereEqualTo("injuryReports", true);

// Send push notification to query


ParsePush push = new ParsePush();
push.setQuery(pushQuery); // Set our
Installation query
push.setMessage("Willie Hayes injured by own
pop fly.");
push.sendInBackground();

We can even use channels with our query. To send a


push to all subscribers of the “Giants” channel but
filtered by those who want score update, we can do the
following:

// Create our Installation query 📋


ParseQuery pushQuery =
ParseInstallation.getQuery();
pushQuery.whereEqualTo("channels", "Giants");
// Set the channel
pushQuery.whereEqualTo("scores", true);

// Send push notification to query


ParsePush push = new ParsePush();
push.setQuery(pushQuery);
push.setMessage("Giants scored against the A's!
It's now 2-2.");
push.sendInBackground();

If we store relationships to other objects in our


ParseInstallation class, we can also use those in our
query. For example, we could send a push notification
to all users near a given location like this.

// Find users near a given location 📋


ParseQuery userQuery = ParseUser.getQuery();
userQuery.whereWithinMiles("location",
stadiumLocation, 1.0)

// Find devices associated with these users


ParseQuery pushQuery =
ParseInstallation.getQuery();
pushQuery.whereMatchesQuery("user", userQuery);

// Send push notification to query


ParsePush push = new ParsePush();
push.setQuery(pushQuery); // Set our
Installation query
push.setMessage("Free hotdogs at the Parse
concession stand!");
push.sendInBackground();

Sending Options

https://docs.parseplatform.org/android/guide/#users 79/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Push notifications can do more than just send a


message. In Android, pushes can also include custom
data you wish to send. You have complete control of
how you handle the data included in your push
notification as we will see in the Receiving
Notifications section. An expiration date can also be
set for the notification in case it is time sensitive.

CUSTOMIZING YOUR NOTIFICATIONS

If you want to send more than just a message, you will


need to use a JSONObject to package all of the data.
There are some reserved fields that have a special
meaning in Android.

alert : the notification’s message.

uri : (Android only) an optional field that


contains a URI. When the notification is opened,
an Activity associated with opening the URI is
launched.

title : (Android, Windows 8, & Windows Phone


8 only) the value displayed in the Android system
tray or Windows 8 toast notification.

For example, to send a notification that would increases


the badge number by 1 and plays a custom sound, you
can do the following. Note that you can set these
properties from your Android client, but they would
only take effect in the iOS version of your app. The
badge and sound fields would have no effects for
Android recipients.

JSONObject data = new JSONObject("{\"alert\": 📋


\"The Mets scored!\",
\"badge\":
\"Increment\",
\"sound\":
\"cheering.caf\"}");

ParsePush push = new ParsePush();


push.setChannel("Mets");
push.setData(data);
push.sendPushInBackground();

It is also possible to specify your own data in this


dictionary. As we’ll see in the Receiving Notifications
section, you’re able to use the data sent with your push

https://docs.parseplatform.org/android/guide/#users 80/171
7/26/22, 5:14 PM Android Developers Guide | Parse

to do custom processing when a user receives and


interacts with a notification.

JSONObject data = new JSONObject("{\"name\": 📋


\"Vaughn\",

\"newsItem\": \"Man bites dog\"}"));

ParsePush push = new ParsePush();


push.setQuery(injuryReportsQuery);
push.setChannel("Indians");
push.setData(data);
push.sendPushInBackground();

SETTING AN EXPIRATION DATE

When a user’s device is turned off or not connected to


the internet, push notifications cannot be delivered. If
you have a time sensitive notification that is not worth
delivering late, you can set an expiration date. This
avoids needlessly alerting users of information that may
no longer be relevant.

There are two methods provided by the ParsePush


class to allow setting an expiration date for your
notification. The first is setExpirationTime which
simply takes an time (in UNIX epoch time) specifying
when Parse should stop trying to send the notification.

// Send push notification with expiration date📋


ParsePush push = new ParsePush();
push.setExpirationTime(1424841505);
push.setQuery(everyoneQuery);
push.setMessage("Season tickets on sale until
February 25th");
push.sendPushInBackground();

There is however a caveat with this method. Since


device clocks are not guaranteed to be accurate, you
may end up with inaccurate results. For this reason, the
ParsePush class also provides the
setExpirationTimeInterval method which accepts a
timeInterval (in seconds). The notification will expire
after the specified interval has elapsed.

// Create time interval 📋


long weekInterval = 60*60*24*7; // 1 week

// Send push notification with expiration


interval

https://docs.parseplatform.org/android/guide/#users 81/171
7/26/22, 5:14 PM Android Developers Guide | Parse
ParsePush push = new ParsePush();
push.setExpirationTimeInterval(weekInterval);
push.setQuery(everyoneQuery);
push.setMessage("Season tickets on sale until
next week!");
push.sendPushInBackground();

TARGETING BY PLATFORM

If you build a cross platform app, it is possible you may


only want to target devices of a particular operating
system. Advanced Targeting allow you to filter which of
these devices are targeted.

The following example would send a different


notification to Android, iOS, and Windows users.

ParseQuery query = 📋
ParseInstallation.getQuery();
query.whereEqualTo("channels",
"suitcaseOwners");

// Notification for Android users


query.whereEqualTo("deviceType", "android");
ParsePush androidPush = new ParsePush();
androidPush.setMessage("Your suitcase has been
filled with tiny robots!");
androidPush.setQuery(query);
androidPush.sendPushInBackground();

// Notification for iOS users


query.whereEqualTo("deviceType", "ios");
ParsePush iOSPush = new ParsePush();
iOSPush.setMessage("Your suitcase has been
filled with tiny apples!");
iOSPush.setQuery(query);
iOSPush.sendPushInBackground();

// Notification for Windows 8 users


query.whereEqualTo("deviceType", "winrt");
ParsePush winPush = new ParsePush();
winPush.setMessage("Your suitcase has been
filled with tiny glass!");
winPush.setQuery(query);
winPush.sendPushInBackground();

// Notification for Windows Phone 8 users


query.whereEqualTo("deviceType", "winphone");
ParsePush wpPush = new ParsePush();
wpPush.setMessage("Your suitcase is very hip;
very metro.");
wpPush.setQuery(query);
wpPush.sendPushInBackground();

Scheduling Pushes
You can schedule a push in advance by specifying a
push time with ParsePush.setPushTime(long) . For
example, if a user schedules a game reminder for a

https://docs.parseplatform.org/android/guide/#users 82/171
7/26/22, 5:14 PM Android Developers Guide | Parse

game tomorrow at noon UTC, you can schedule the


push notification by sending:

long tomorrowTime = ...; // in seconds 📋


// Send push notification with expiration
interval
ParsePush push = new ParsePush();
push.setPushTime(tomorrowTime);
push.setMessage("You previously created a
reminder for the game today");
push.sendPushInBackground();

If you also specify an expiration interval, it will be


calculated from the scheduled push time, not from the
time the push is submitted. This means a push
scheduled to be sent in a week with an expiration
interval of a day will expire 8 days after the request is
sent.

The scheduled time cannot be in the past, and can be


up to two weeks in the future.

Receiving Pushes
Make sure you’ve gone through the Android Push
QuickStart to set up your app to receive pushes.

When a push notification is received, the “title” is


displayed in the status bar and the “alert” is displayed
alongside the “title” when the user expands the
notification drawer. If you choose to subclass
com.parse.ParsePushBroadcastReceiver , be sure to
replace that name with your class’ name in the
registration.

Note that some Android emulators (the ones without


Google API support) don’t support FCM, so if you test
your app in an emulator make sure to select an
emulator image that has Google APIs installed.

CUSTOMIZING NOTIFICATIONS

Now that your app is all set up to receive push


notifications, you can start customizing the display of
these notifications.

https://docs.parseplatform.org/android/guide/#users 83/171
7/26/22, 5:14 PM Android Developers Guide | Parse
CUSTOMIZING NOTIFICATION ICONS

The Android style guide recommends apps use a push


icon that is monochromatic and flat. The default push
icon is your application’s launcher icon, which is unlikely
to conform to the style guide. To provide a custom push
icon, add the following metadata tag to your app’s
AndroidManifest.xml :

<meta-data 📋
android:name="com.parse.push.notification_icon"
android:resource="@drawable/push_icon"/>

…where push_icon is the name of a drawable resource


in your package. If your application needs more than
one small icon, you can override getSmallIconId in
your ParsePushBroadcastReceiver subclass.

If your push has a unique context associated with an


image, such as the avatar of the user who sent a
message, you can use a large push icon to call attention
to the notification. When a notification has a large push
icon, your app’s static (small) push icon is moved to the
lower right corner of the notification and the large icon
takes its place. See the Android UI documentation for
examples. To provide a large icon, you can override
getLargeIcon in your ParsePushBroadcastReceiver
subclass.

RESPONDING WITH A CUSTOM ACTIVITY

If your push has no “uri” parameter, onPushOpen will


invoke your application’s launcher activity. To customize
this behavior, you can override getActivity in your
ParsePushBroadcastReceiver subclass.

RESPONDING WITH A URI

If you provide a “uri” field in your push, the


ParsePushBroadcastReceiver will open that URI when
the notification is opened. If there are multiple apps
capable of opening the URI, a dialog will displayed for
the user. The ParsePushBroadcastReceiver will
manage your back stack and ensure that clicking back
from the Activity handling URI will navigate the user
back to the activity returned by getActivity .

https://docs.parseplatform.org/android/guide/#users 84/171
7/26/22, 5:14 PM Android Developers Guide | Parse
MANAGING THE PUSH LIFECYCLE

The push lifecycle has three phases:

1 A notification is received and the


com.parse.push.intent.RECEIVE Intent is fired,
causing the ParsePushBroadcastReceiver to call
onPushReceive . If either “alert” or “title” are
specified in the push, then a Notification is
constructed using getNotification . This
Notification uses a small icon generated using
getSmallIconId , which defaults to the icon
specified by the
com.parse.push.notification_icon metadata
in your AndroidManifest.xml . The Notification’s
large icon is generated from getLargeIcon which
defaults to null. The notification’s contentIntent
and deleteIntent are
com.parse.push.intent.OPEN and
com.parse.push.intent.DELETE respectively.

2 If the user taps on a Notification, the


com.parse.push.intent.OPEN Intent is fired. The
ParsePushBroadcastReceiver calls onPushOpen .
The default implementation automatically sends
an analytics event back to Parse tracking that this
notification was opened. If the push contains a
“uri” parameter, an activity is launched to
navigate to that URI, otherwise the activity
returned by getActivity is launched.

3 If the user dismisses a Notification, the


com.parse.push.intent.DELETE Intent is fired.
The ParsePushBroadcastReceiver calls
onPushDismiss , which does nothing by default

All of the above methods may be subclassed to


customize the way your application handles push
notifications. When subclassing the methods
onPushReceive , onPushOpen , onPushDismiss , or
getNotification , consider delegating to super where
appropriate. For example, one might override
onPushReceive to trigger a background operation for
“silent” pushes and then delegate to super for all other
pushes. This provides the most benefit from Parse Push
and makes your code forward-compatible.

TRACKING PUSHES AND APP OPENS

The default implementation of onPushOpen will


automatically track user engagement from pushes. If
https://docs.parseplatform.org/android/guide/#users 85/171
7/26/22, 5:14 PM Android Developers Guide | Parse

you choose not to use the


ParsePushBroadcastReceiver or override the
onPushOpen implementation, you may need to track
your app open event manually. To do this, add the
following to the onCreate method of the Activity or
the onReceive method of the BroadcastReceiver
which handles the com.parse.push.intent.OPEN
Intent:

ParseAnalytics.trackAppOpened(getIntent()); 📋

To track push opens, you should always pass the


Intent to trackAppOpened . Passing null to
trackAppOpened will track only a standard app-opened
event, not the push-opened event. If you don’t track the
push-opened event, you will not be able to use
advanced analytics features such as push-open graphs
and A/B testing.

Please be sure to set up your application to save the


Installation object. Push open tracking only works
when your application’s devices are associated with
saved Installation objects.

You can view the open rate for a specific push


notification on your Parse Dashboard push console. You
can also view your application’s overall app open and
push open graphs on the Parse analytics console. Our
push open analytics graphs are rendered in real time, so
you can easily verify that your application is sending the
correct analytics events before your next release.

Push Experiments
You can A/B test your push notifications to figure out
the best way to keep your users engaged. With A/B
testing, you can simultaneously send two versions of
your push notification to different devices, and use each
version’s push open rates to figure out which one is
better. You can test by either message or send time.

A/B TESTING

https://docs.parseplatform.org/android/guide/#users 86/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Our web push console guides you through every step of


setting up an A/B test.

For each push campaign sent through the Parse web


push console, you can allocate a subset of your devices
to be in the experiment’s test audience, which Parse will
automatically split into two equally-sized experiment
groups. For each experiment group, you can specify a
different push message. The remaining devices will be
saved so that you can send the winning message to
them later. Parse will randomly assign devices to each
group to minimize the chance for a test to affect
another test’s results (although we still don’t
recommend running multiple A/B tests over the same
devices on the same day).

After you send the push, you can come back to the
push console to see in real time which version resulted
in more push opens, along with other metrics such as
statistical confidence interval. It’s normal for the
number of recipients in each group to be slightly
different because some devices that we had originally
allocated to that experiment group may have
uninstalled the app. It’s also possible for the random
group assignment to be slightly uneven when the test
audience size is small. Since we calculate open rate
separately for each group based on recipient count, this
should not significantly affect your experiment results.

If you are happy with the way one message performed,


you can send that to the rest of your app’s devices (i.e.
the “Launch Group”). This step only applies to A/B tests
where you vary the message.

Push experiments are supported on all recent Parse


SDKs (iOS v1.2.13+, Android v1.4.0+, .NET v1.2.7+).
Before running experiments, you must instrument your
app with push open tracking.

EXPERIMENT STATISTICS

https://docs.parseplatform.org/android/guide/#users 87/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Parse provides guidance on how to run experiments to


achieve statistically significant results.

TEST AUDIENCE SIZE

When you setup a push message experiment, we’ll


recommend the minimum size of your test audience.
These recommendations are generated through
simulations based on your app’s historical push open
rates. For big push campaigns (e.g. 100k+ devices), this
recommendation is usually small subset of your devices.
For smaller campaigns (e.g. < 5k devices), this
recommendation is usually all devices. Using all devices
for your test audience will not leave any remaining
devices for the launch group, but you can still gain
valuable insight into what type of messaging works
better so you can implement similar messaging in your
next push campaign.

CONFIDENCE INTERVAL

After you send your pushes to experiment groups, we’ll


also provide a statistical confidence interval when your
experiment has collected enough data to have
statistically significant results. This confidence interval
is in absolute percentage points of push open rate (e.g.
if the open rates for groups A and B are 3% and 5%,
then the difference is reported as 2 percentage points).
This confidence interval is a measure of how much
difference you would expect to see between the two
groups if you repeat the same experiment many times.

Just after a push send, when only a small number of


users have opened their push notifications, the open
rate difference you see between groups A and B could
be due to random chance, so it might not be
reproducible if you run the same experiment again.
After your experiment collects more data over time, we
become increasingly confident that the observed
difference is a true difference. As this happens, the
confidence interval will become narrower, allowing us
to more accurately estimate the true difference
between groups A and B. Therefore, we recommend
that you wait until there is enough data to generate a

https://docs.parseplatform.org/android/guide/#users 88/171
7/26/22, 5:14 PM Android Developers Guide | Parse

statistical confidence interval before deciding which


group’s push is better.

Push Localization
Localizing your app’s content is a proven way to drive
greater engagement. We’ve made it easy to localize
your push messages with Push Localization. The latest
version of the Parse Android SDK will detect and store
the user’s language in the installation object, and via the
web push console you’ll be able to send localized push
messages to your users in a single broadcast.

SETUP FOR LOCALIZED PUSH

To take advantage of Push Localization you will need to


make sure you’ve published your app with the Parse
Android SDK version 1.10.1 or greater. Any users of your
application running the Parse Android SDK version
1.10.1 or greater will then be targetable by Push
Localization via the web push console.

It’s important to note that for developers who have


users running apps with versions of the Parse Android
SDK earlier than 1.10.1 that targeting information for
Localized Push will not be available and these users will
receive the default message from the push console.

SENDING A LOCALIZED PUSH

Our web push console guides you through every step of


setting up a Localized Push.

Troubleshooting
Setting up Push Notifications is often a source of
frustration for developers. The process is complicated
and invites problems to happen along the way. If you
run into issues, try some of these troubleshooting tips.

It’s important to break down the system into


components when troubleshooting push notification
issues. You can start by asking yourself the following
questions:

https://docs.parseplatform.org/android/guide/#users 89/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Is the push notification listed in your push logs?

Are you targeting your push notification to the


correct audience?

Are your devices registering for push


notifications?

Is your app set up to receive these notifications?

If you’re unsure about the answer to any of the above


questions, read on!

CONFIRM THAT THE PUSH CAMPAIGN WAS CREATED

Having everything set up correctly in your Parse app


won’t help if your request to send a push notification
does not reach Parse. The first step in debugging a
push issue is to confirm that the push campaign is listed
in your push logs. You can find these logs by visiting
your app’s Dashboard and clicking on Push.

If the push notification campaign is not showing up on


that list, the issue is quite simple to resolve. Go back to
your push notification sending code and make sure to
check for any error responses. If you’re using any of the
client SDKs, make sure to listen for and catch any errors
that may be returned. For example, you could log errors
like so:

push.sendPushInBackground(new SendCallback() {📋
public void done(ParseException e) {
if (e == null) {
Log.d("push", "The push campaign has been
created.");
} else {
Log.d("push", "Error sending push:" +
e.getMessage());
}
}
});

Please note that SDKs that use a Client Key, such as the
Android SDK, can only send push notifications if Client
Push is enabled in your Parse app’s Push Notification
settings. Otherwise, you’ll only be able to send pushes
from the web console, the REST API, or by using the
JavaScript SDK from Cloud Code. We strongly
encourage developers to turn off Client Push before
releasing their app publicly unless your use case allows

https://docs.parseplatform.org/android/guide/#users 90/171
7/26/22, 5:14 PM Android Developers Guide | Parse

for any amount of arbitrary pushes to be sent by any of


your users. You can read our security guide for more
information.

VERIFY YOUR TARGETING

You have confirmed that the push notification is making


it to your push logs. Now what? The next step is to
verify if your push notification targeting is correct. Say,
if you’re debugging why notifications are not reaching a
specific device, it’s important to make sure that this
device is actually included in the targeting of this push
notification. You can’t deliver something that is not
addressed correctly.

In order to do this, you’ll have to confirm that the


device’s ParseInstallation object is included in your
push notification targeting criteria. This is quite
straightforward if you’re using channels: all you need to
verify is that your device’s ParseInstallation is
subscribed to the channel by checking the channels
array. If you’re using advanced targeting, e.g. you’re
using a push query with constraints, you’ll have to work
a bit more to determine if your targeting is on point.

Basically, you will need to run the same push query


you’re using for your targeting, and verify that your
installation of interest is included in the result set. As
you can only query the installation class
programmatically using the Master Key, you will need to
use one of the Master Key capable SDKs (JavaScript
SDK, .NET) or the REST API. Ideally, you would use the
same SDK that you’re currently using to send the push
notifications.

DEBUGGING USING THE REST API

The REST API is quite easy to use for this sort of


purpose as you can easily recreate the push query using
the information provided in your push notification logs.
If you look closely at the “Full Target” value in your push
campaign log item, you may notice that it matches the
query format for a REST API query. You can grab an
example of what a REST API query over
ParseInstallation s would look like from the REST
API docs. Don’t forget to use the X-Parse-Master-Key
https://docs.parseplatform.org/android/guide/#users 91/171
7/26/22, 5:14 PM Android Developers Guide | Parse

header to ensure that the Master Key is used to run this


query.

# Query over installations 📋


curl -X GET \
-H "X-Parse-Application-Id:
{YOUR_APPLICATION_ID}" \
-H "X-Parse-Master-Key: {YOUR_MASTER_KEY}" \
-G \
--data-urlencode 'limit=1000' \
--data-urlencode 'where={ "city": "San
Francisco", "deviceType": { "$in": [ "ios",
"android", "winphone", "embedded" ] } }' \
https://YOUR.PARSE-
SERVER.HERE/parse/installations

If you type the above into a console, you should be able


to see the first 1,000 objects that match your query.
Note that constraints are always ANDed, so if you want
to further reduce the search scope, you can add a
constraint that matches the specific installation for
your device:

# Query over installations 📋


curl -X GET \
-H "X-Parse-Application-Id:
{YOUR_APPLICATION_ID}" \
-H "X-Parse-Master-Key: {YOUR_MASTER_KEY}" \
-G \
--data-urlencode 'limit=1' \
--data-urlencode 'where={ “objectId”:
{YOUR_INSTALLATION_OBJECT_ID}, "city": "San
Francisco", "deviceType": { "$in": [ "ios",
"android", "winphone", "embedded" ] } }' \
https://YOUR.PARSE-
SERVER.HERE/parse/installations

If the above query returns no results, it is likely that


your installation does not meet the targeting criteria for
your campaign.

CHECK YOUR DEVICE CONFIGURATION

Your push campaign is created and the device is


included in the targeting, but you’re still not receiving
push notifications. What gives?

You can check the Push Delivery Report for the cause
of failed deliveries: Mismatch Sender ID happens when
the registered sender is not the same as the one
currently sending the notifications, Not Registered is
returned when the registration key is deemed invalid,

https://docs.parseplatform.org/android/guide/#users 92/171
7/26/22, 5:14 PM Android Developers Guide | Parse

either by misconfiguration or if the user have


uninstalled the app.

If everything looks great so far, but push notifications


are not showing up on your phone, there are a few
more things you can check.

Upgrade to the latest SDK. This documentation


covers the push API introduced in the 1.17.0
version of the Android Parse SDK. Please upgrade
if you are getting compiler errors following these
instructions.

Make sure you’ve used the correct App ID and


client key, and that Parse.initialize() is being
called. Parse.initialize() lets the service
know which application it is listening for; this
code must be in your Application.onCreate
rather than Activity.onCreate for a particular
Activity , so that any activation technique will
know how to use Parse.

Check that the push registration call is being


called successfully. Your device must successfully
register a ParseInstallation object with a valid
FCM Registration id in the “deviceToken” field

Check that the device is set to accept push


notifications from your app.

Note that, by design, force-killed apps will not be


able to receive push notifications. Launch the app
again to reenable push notifications.

Check the number of subscribers in your Parse


Push Console. Does it match the expected
number of subscribers? Your push might be
targeted incorrectly.

If testing in the emulator, try cleaning and


rebuilding your project and restarting your AVD.

Turn on verbose logging with


Parse.setLogLevel(Parse.LOG_LEVEL_VERBOSE) .
The error messages will be a helpful guide to
what may be going on.

If you see the message “Finished (with error)” in


your Dashboard, check your verbose logs. If you
are performing pushes from a device, check that
client-side push is enabled in your dashboard.

In your logs, you may see an error message,


“Could not construct writer” or other issues
related to a broken pipe. When this occurs, the
https://docs.parseplatform.org/android/guide/#users 93/171
7/26/22, 5:14 PM Android Developers Guide | Parse

framework will continue to try reconnecting. It


should not crash your app.

If your app has been released for a while, it is expected


that a percentage of your user base will have opted out
of push notifications from your app or uninstalled your
app from their device. Parse does not automatically
delete installation objects in either of these cases.
When a push campaign is sent out, Parse will detect
uninstalled installations and exclude them from the
total count of push notifications sent. The recipient
estimate on the push composer is based on the
estimated number of installations that match your
campaign’s targeting criteria. With that in mind, it is
possible for the recipient estimate to be higher than the
number of push notifications that is sent as reported by
the push campaign status page.

Want to contribute to this doc? Edit this section.

Config
Parse Config
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");

https://docs.parseplatform.org/android/guide/#users 94/171
7/26/22, 5:14 PM Android Developers Guide | Parse
Log.d("TAG", String.format("Yay! The number
is %d!", number));
}
});

Retrieving Config
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));
}
});

Current Config
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

https://docs.parseplatform.org/android/guide/#users 95/171
7/26/22, 5:14 PM Android Developers Guide | Parse

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();
}
}
}

Parameters
ParseConfig supports most of the data types
supported by ParseObject :

String

Numbers (boolean/int/double/long)

Date

ParseFile

ParseGeoPoint

List

Map

We currently allow up to 100 parameters in your config


and a total size of 128KB across all parameters.

Want to contribute to this doc? Edit this section.

Analytics
Parse provides a number of hooks for you to get a
glimpse into the ticking heart of your app. We

https://docs.parseplatform.org/android/guide/#users 96/171
7/26/22, 5:14 PM Android Developers Guide | Parse

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.

App-Open & Push Analytics


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.trackAppOpenedInBackground(getInten

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.

Custom Analytics
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.

https://docs.parseplatform.org/android/guide/#users 97/171
7/26/22, 5:14 PM Android Developers Guide | Parse

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() .

Want to contribute to this doc? Edit this section.

User Interface
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.

ParseLoginUI

https://docs.parseplatform.org/android/guide/#users 98/171
7/26/22, 5:14 PM Android Developers Guide | Parse

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 that does exactly
this. Please note ParseLoginUI is not included with the
Parse Android SDK; you need to import this library from
maven or from our Git repository into your project.
Check the README.md of the ParseLoginUI for detail
steps.

This library 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.

Login Library API


BASIC INTEGRATION

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

https://docs.parseplatform.org/android/guide/#users 99/171
7/26/22, 5:14 PM Android Developers Guide | Parse
android:name="com.parse.ui.ParseLoginActivity.PARS

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 : The user successfully logged in.


ParseUser.getCurrentUser() will be populated.

RESULT_CANCELLED : The user pressed the back


button. If the user fails to log in or sign up, the
only way to return to the previous screen is by
pressing the back button.

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.

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.

ADVANCED INTEGRATION

https://docs.parseplatform.org/android/guide/#users 100/171
7/26/22, 5:14 PM Android Developers Guide | Parse

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 .

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.

https://docs.parseplatform.org/android/guide/#users 101/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Customizing the Login Library


There are three ways to customize the login library:

Activity metadata in 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 .

In code with ParseLoginBuilder - If you love


writing code to do everything.

Overriding layout resource XMLs - This option is


useful if you want to make significant changes to
the look and feel of the login experience.

CONFIGURE BY ACTIVITY METADATA

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)

https://docs.parseplatform.org/android/guide/#users 102/171
7/26/22, 5:14 PM Android Developers Guide | Parse

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_

android:resource="@drawable/my_app_logo"/>
<!-- For these non-resource options, use
android:value -->
<meta-data

android:name="com.parse.ui.ParseLoginActivity.PARS

android:value="true"/>
<meta-data
https://docs.parseplatform.org/android/guide/#users 103/171
7/26/22, 5:14 PM Android Developers Guide | Parse

android:name="com.parse.ui.ParseLoginActivity.PARS

android:value="true"/>
<meta-data

android:name="com.parse.ui.ParseLoginActivity.PARS

android:value="@string/password_reset_text"/>
<meta-data

android:name="com.parse.ui.ParseLoginActivity.MIN_

android:value="8"/>
<meta-data

android:name="com.parse.ui.ParseLoginActivity.FACE

android:value="true"/>
<!-- We reference a string-array resource
here, so we must use android:resource -->
<meta-data

android:name="com.parse.ui.ParseLoginActivity.FACE

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>

CONFIGURE BY CODE

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.

https://docs.parseplatform.org/android/guide/#users 104/171
7/26/22, 5:14 PM Android Developers Guide | Parse

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
"user_friends"))
.setTwitterLoginEnabled(true)
.setTwitterLoginButtontext("Twitter")
.build();
startActivityForResult(parseLoginIntent, 0);

CONFIGURE BY OVERRIDING LAYOUT RESOURCE


FILES

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 : If
you do not use certain login methods
(username/password, Facebook, or Twitter), you
can remove the corresponding UI elements from
this layout.

com_parse_ui_parse_signup_fragment.xml : You
can add additional input fields in the signup form
here. If you do, you also need add code to
ParseSignupFragment to copy that data into the
ParseUser object.

com_parse_ui_parse_login_help_fragment.xml :
You can change the message for password reset.

We’ve provided another sample app,


ParseLoginSampleLayoutOverride showing how to do
this. This sample app only has a Facebook login button

https://docs.parseplatform.org/android/guide/#users 105/171
7/26/22, 5:14 PM Android Developers Guide | Parse

in com_parse_ui_parse_login_fragment.xml , and adds


a background image to the login screens.

ParseQueryAdapter (Deprecated 0.0.4)


Since RecyclerView is now favorable over ListView , it
is recommended to use a custom RecyclerView.Adapter
instead of using ParseQueryAdapter .

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 :

1 Instantiate a ParseQueryAdapter .

2 Customize it as necessary (see the below


subsections for detailed instructions to display
data from specific queries, change the UI of the
View s to be displayed, and more).

3 Set your new 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:

1 Pagination, with a row that can be tapped to load


the next page.

2 Configurable downloading and displaying of


remote images within rows.

3 Automatic loading and management of the Parse


objects array.

4 Callbacks from major events in the data cycle.

https://docs.parseplatform.org/android/guide/#users 106/171
7/26/22, 5:14 PM Android Developers Guide | Parse

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<ParseObject> adapter = new


ParseQueryAdapter<ParseObject>(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.

Customizing the Query


By default, the simplest ParseQueryAdapter
constructor takes a Context and a Parse class name.
All ParseObject 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
https://docs.parseplatform.org/android/guide/#users 107/171
7/26/22, 5:14 PM Android Developers Guide | Parse

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<ParseObject> adapter = 📋
new ParseQueryAdapter<ParseObject>(this, new
ParseQueryAdapter.QueryFactory<ParseObject>() {
public ParseQuery<ParseObject> 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;
}
});

Customizing the Rows


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 ParseObject 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(ParseObject, 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(ParseObject, View, ViewGroup) and
getNextPageView(View, ViewGroup) expect a
TextView (id: android.R.id.text1 ) if the textKey is

https://docs.parseplatform.org/android/guide/#users 108/171
7/26/22, 5:14 PM Android Developers Guide | Parse

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(ParseObject 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("descript

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 ParseObject :

@Override 📋
public View getItemView(ParseObject 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.
https://docs.parseplatform.org/android/guide/#users 109/171
7/26/22, 5:14 PM Android Developers Guide | Parse
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;
}

Loading Remote Images in Rows


ParseQueryAdapter makes it simple to display remote
images. By calling setImageKey(String) , you can pass
in a key name on your ParseObject 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 ParseObject 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.

Lifecycle Methods
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<ParseObject>, Exception) methods
for implementation.

https://docs.parseplatform.org/android/guide/#users 110/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Pagination
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.

Auto-loading of Data
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.

Want to contribute to this doc? Edit this section.

Data
We’ve designed the Parse SDKs so that you typically
don’t need to worry about how data is saved while
using the client SDKs. Simply add data to the Parse
Object , and it’ll be saved correctly.

Nevertheless, there are some cases where it’s useful to


be aware of how data is stored on the Parse platform.

Data Storage

https://docs.parseplatform.org/android/guide/#users 111/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Internally, Parse stores data as JSON, so any datatype


that can be converted to JSON can be stored on Parse.
Refer to the Data Types in Objects section of this
guide to see platform-specific examples.

Keys including the characters $ or . , along with the


key __type key, are reserved for the framework to
handle additional types, so don’t use those yourself. Key
names must contain only numbers, letters, and
underscore, and must start with a letter. Values can be
anything that can be JSON-encoded.

Data Type Lock-in


When a class is initially created, it doesn’t have an
inherent schema defined. This means that for the first
object, it could have any types of fields you want.

However, after a field has been set at least once, that


field is locked into the particular type that was saved.
For example, if a User object is saved with field name
of type String , that field will be restricted to the
String type only (the server will return an error if you
try to save anything else).

One special case is that any field can be set to null , no


matter what type it is.

The Data Browser


The Data Browser is the web UI where you can update
and create objects in each of your apps. Here, you can
see the raw JSON values that are saved that represents
each object in your class.

When using the interface, keep in mind the following:

The objectId , createdAt , updatedAt fields


cannot be edited (these are set automatically).

The value “(empty)” denotes that the field has


not been set for that particular object (this is
different than null ).

You can remove a field’s value by hitting your


Delete key while the value is selected.

https://docs.parseplatform.org/android/guide/#users 112/171
7/26/22, 5:14 PM Android Developers Guide | Parse

The Data Browser is also a great place to test the Cloud


Code validations contained in your Cloud Code
functions (such as beforeSave ). These are run
whenever a value is changed or object is deleted from
the Data Browser, just as they would be if the value was
changed or deleted from your client code.

Importing Data
You may import data into your Parse app by using CSV
or JSON files. To create a new class with data from a
CSV or JSON file, go to the Data Browser and click the
“Import” button on the left hand column.

The JSON format is an array of objects in our REST


format or a JSON object with a results that contains
an array of objects. It must adhere to the JSON
standard. A file containing regular objects could look
like:

{ "results": [ 📋
{
"score": 1337,
"playerName": "Sean Plott",
"cheatMode": false,
"createdAt": "2022-01-01T12:23:45.678Z",
"updatedAt": "2022-01-01T12:23:45.678Z",
"objectId": "fchpZwSuGG"
}]
}

Objects in either format should contain keys and values


that also satisfy the following:

Key names must contain only numbers, letters,


and underscore, and must start with a letter.

No value may contain a hard newline ‘ \n ’.

Normally, when objects are saved to Parse, they are


automatically assigned a unique identifier through the
objectId field, as well as a createdAt field and
updatedAt field which represent the time that the

https://docs.parseplatform.org/android/guide/#users 113/171
7/26/22, 5:14 PM Android Developers Guide | Parse

object was created and last modified in your Parse


Server. These fields can be manually set when data is
imported from a JSON file. Please keep in mind the
following:

Use a unique 10 character alphanumeric string as


the value of your objectId fields.

Use a UTC timestamp in the ISO 8601 format


when setting a value for the createdAt field or
the updatedAt field.

In addition to the exposed fields, objects in the Parse


User class can also have the bcryptPassword field set.
The value of this field is a String that is the bcrypt
hashed password + salt in the modular crypt format
described in this StackOverflow answer. Most
OpenSSL based bcrypt implementations should have
built-in methods to produce these strings.

A file containing a User object could look like:

{ "results": 📋
[{
"username": "cooldude",
"createdAt": "1983-09-13T22:42:30.548Z",
"updatedAt": "2015-09-04T10:12:42.137Z",
"objectId": "ttttSEpfXm",
"sessionToken":
"dfwfq3dh0zwe5y2sqv514p4ib",
"bcryptPassword":
"$2a$10$ICV5UeEf3lICfnE9W9pN9.O9Ved/ozNo7G83Qbdk5r

}]
}

Note that in CSV the import field types are limited to


String , Boolean , and Number .

Exporting your Data


You can request an export of your data at any time
from your app’s Settings page. The data export runs at
a lower priority than production queries, so if your app
https://docs.parseplatform.org/android/guide/#users 114/171
7/26/22, 5:14 PM Android Developers Guide | Parse

is still serving queries, production traffic will always be


given a higher priority, which may slow down the
delivery of your data export.

EXPORT FORMATS

Each collection will be exported in the same JSON


format used by our REST API and delivered in a single
zipped file. Since data is stored internally as JSON, this
allows us to ensure that the export closely matches
how the data is saved to Parse. Other formats such as
CSV cannot represent all of the data types supported
by Parse without losing information. If you’d like to
work with your data in CSV format, you can use any of
the JSON-to-CSV converters available widely on the
web.

OFFLINE ANALYSIS

For offline analysis of your data, we highly recommend


using alternate ways to access your data that do not
require extracting the entire collection at once. For
example, you can try exporting only the data that has
changed since your last export. Here are some ways of
achieving this:

Use the JavaScript SDK in a node app.


Parse.Query.each() will allow you to extract
every single object that matches a query. You can
use date constraints to make sure the query only
matches data that has been updated since you
last ran this app. Your node app can write this
data to disk for offline analysis.

Use the REST API in a script. You can run queries


against your class and use skip/limit to page
through results, which can then be written to disk
for offline analysis. You can again use date
constraints to make sure only newly updated data
is extracted.

If the above two options do not fit your needs,


you can try using the Data Browser to export data
selectively. Use the Funnel icon to create a filter
for the specific data that you need to export, such

https://docs.parseplatform.org/android/guide/#users 115/171
7/26/22, 5:14 PM Android Developers Guide | Parse

as newly updated objects. Once the filter has


been applied, click on the Export data icon on the
upper right of your Data Browser. This type of
export will only include the objects that match
your criteria.

Want to contribute to this doc? Edit this section.

Relations
There are three kinds of relationships. One-to-one
relationships enable one object to be associated with
another object. One-to-many relationships enable one
object to have many related objects. Finally, many-to-
many relationships enable complex relationships among
many objects.

There are four ways to build relationships in Parse:

One-to-Many
When you’re thinking about one-to-many relationships
and whether to implement Pointers or Arrays, there are
several factors to consider. First, how many objects are
involved in this relationship? If the “many” side of the
relationship could contain a very large number (greater
than 100 or so) of objects, then you have to use
Pointers. If the number of objects is small (fewer than
100 or so), then Arrays may be more convenient,
especially if you typically need to get all of the related
objects (the “many” in the “one-to-many relationship”)
at the same time as the parent object.

USING POINTERS

Let’s say we have a game app. The game keeps track of


the player’s score and achievements every time she
chooses to play. In Parse, we can store this data in a
single Game object. If the game becomes incredibly
successful, each player will store thousands of Game
objects in the system. For circumstances like this,

https://docs.parseplatform.org/android/guide/#users 116/171
7/26/22, 5:14 PM Android Developers Guide | Parse

where the number of relationships can be arbitrarily


large, Pointers are the best option.

Suppose in this game app, we want to make sure that


every Game object is associated with a Parse User. We
can implement this like so:

ParseObject game = new ParseObject("Game"); 📋


game.put("createdBy",
ParseUser.getCurrentUser());

We can obtain all of the Game objects created by a


Parse User with a query:

ParseQuery<ParseObject> gameQuery = 📋
ParseQuery.getQuery("Game");
gameQuery.whereEqualTo("createdBy",
ParseUser.getCurrentUser());

And, if we want to find the Parse User who created a


specific Game , that is a lookup on the createdBy key:

// say we have a Game object 📋


ParseObject game = ...

// getting the user who created the Game


ParseUser createdBy =
game.getUser("createdBy");

For most scenarios, Pointers will be your best bet for


implementing one-to-many relationships.

USING ARRAYS

Arrays are ideal when we know that the number of


objects involved in our one-to-many relationship are
going to be small. Arrays will also provide some
productivity benefit via the includeKey parameter.
Supplying the parameter will enable you to obtain all of
the “many” objects in the “one-to-many” relationship at
the same time that you obtain the “one” object.
However, the response time will be slower if the
number of objects involved in the relationship turns out
to be large.

https://docs.parseplatform.org/android/guide/#users 117/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Suppose in our game, we enabled players to keep track


of all the weapons their character has accumulated as
they play, and there can only be a dozen or so weapons.
In this example, we know that the number of weapons
is not going to be very large. We also want to enable
the player to specify the order in which the weapons
will appear on screen. Arrays are ideal here because the
size of the array is going to be small and because we
also want to preserve the order the user has set each
time they play the game:

Let’s start by creating a column on our Parse User


object called weaponsList .

Now let’s store some Weapon objects in the


weaponsList :

// let's say we have four weapons 📋


ParseObject scimitar = ...
ParseObject plasmaRifle = ...
ParseObject grenade = ...
ParseObject bunnyRabbit = ...

// stick the objects in an array


ArrayList<ParseObject> weapons = new
ArrayList<ParseObject>();
weapons.add(scimitar);
weapons.add(plasmaRifle);
weapons.add(grenade);
weapons.add(bunnyRabbit);

// store the weapons for the user


ParseUser.getCurrentUser().put("weaponsList",
weapons);

Later, if we want to retrieve the Weapon objects, it’s


just one line of code:

ArrayList<ParseObject> weapons = 📋
ParseUser.getCurrentUser().get("weaponsList");

Sometimes, we will want to fetch the “many” objects in


our one-to-many relationship at the same time as we
fetch the “one” object. One trick we could employ is to
use the includeKey (or include in Android)
parameter whenever we use a Parse Query to also
fetch the array of Weapon objects (stored in the
weaponsList column) along with the Parse User
object:

https://docs.parseplatform.org/android/guide/#users 118/171
7/26/22, 5:14 PM Android Developers Guide | Parse

// set up our query for a User object 📋


ParseQuery<ParseUser> userQuery =
ParseUser.getQuery();

// configure any constraints on your query...


// for example, you may want users who are also
playing with or against you
// tell the query to fetch all of the Weapon
objects along with the user
// get the "many" at the same time that you're
getting the "one"
userQuery.include("weaponsList");

// execute the query


userQuery.findInBackground(new
FindCallback<ParseUser>() {
public void done(List<ParseUser> userList,
ParseException e) {
// userList contains all of the User
objects, and their associated Weapon objects,
too
}
});

You can also get the “one” side of the one-to-many


relationship from the “many” side. For example, if we
want to find all Parse User objects who also have a
given Weapon , we can write a constraint for our query
like this:

// add a constraint to query for whenever a 📋


specific Weapon is in an array
userQuery.whereEqualTo("weaponsList",
scimitar);

// or query using an array of Weapon objects...


userQuery.whereEqualTo("weaponsList",
arrayOfWeapons);

Many-to-Many
Now let’s tackle many-to-many relationships. Suppose
we had a book reading app and we wanted to model
Book objects and Author objects. As we know, a given
author can write many books, and a given book can
have multiple authors. This is a many-to-many
relationship scenario where you have to choose
between Arrays, Parse Relations, or creating your own
Join Table.

The decision point here is whether you want to attach


any metadata to the relationship between two entities.
If you don’t, Parse Relation or using Arrays are going to
be the easiest alternatives. In general, using arrays will

https://docs.parseplatform.org/android/guide/#users 119/171
7/26/22, 5:14 PM Android Developers Guide | Parse

lead to higher performance and require fewer queries.


If either side of the many-to-many relationship could
lead to an array with more than 100 or so objects, then,
for the same reason Pointers were better for one-to-
many relationships, Parse Relation or Join Tables will be
better alternatives.

On the other hand, if you want to attach metadata to


the relationship, then create a separate table (the “Join
Table”) to house both ends of the relationship.
Remember, this is information about the relationship,
not about the objects on either side of the relationship.
Some examples of metadata you may be interested in,
which would necessitate a Join Table approach, include:

USING PARSE RELATIONS

Using Parse Relations, we can create a relationship


between a Book and a few Author objects. In the Data
Browser, you can create a column on the Book object
of type relation and name it authors .

After that, we can associate a few authors with this


book:

📋
// let’s say we have a few objects representing
Author objects
ParseObject authorOne =
ParseObject authorTwo =
ParseObject authorThree =

// now we create a book object


ParseObject book = new ParseObject("Book");

// now let’s associate the authors with the


book
// remember, we created a "authors" relation on
Book
ParseRelation<ParseObject> relation =
book.getRelation("authors");
relation.add(authorOne);
relation.add(authorTwo);
relation.add(authorThree);

// now save the book object


book.saveInBackground();

To get the list of authors who wrote a book, create a


query:

// suppose we have a book object 📋


ParseObject book = ...

https://docs.parseplatform.org/android/guide/#users 120/171
7/26/22, 5:14 PM Android Developers Guide | Parse
// create a relation based on the authors key
ParseRelation relation =
book.getRelation("authors");

// generate a query based on that relation


ParseQuery query = relation.getQuery();

// now execute the query

Perhaps you even want to get a list of all the books to


which an author contributed. You can create a slightly
different kind of query to get the inverse of the
relationship:

// suppose we have a author object, for which 📋


we want to get all books
ParseObject author = ...

// first we will create a query on the Book


object
ParseQuery<ParseObject> query =
ParseQuery.getQuery("Book");

// now we will query the authors relation to


see if the author object we have
// is contained therein
query.whereEqualTo("authors", author);

USING JOIN TABLES

There may be certain cases where we want to know


more about a relationship. For example, suppose we
were modeling a following/follower relationship
between users: a given user can follow another user,
much as they would in popular social networks. In our
app, we not only want to know if User A is following
User B, but we also want to know when User A started
following User B. This information could not be
contained in a Parse Relation. In order to keep track of
this data, you must create a separate table in which the
relationship is tracked. This table, which we will call
Follow , would have a from column and a to column,
each with a pointer to a Parse User. Alongside the
relationship, you can also add a column with a Date
object named date .

Now, when you want to save the following relationship


between two users, create a row in the Follow table,
filling in the from , to , and date keys appropriately:

https://docs.parseplatform.org/android/guide/#users 121/171
7/26/22, 5:14 PM Android Developers Guide | Parse

// suppose we have a user we want to follow 📋


ParseUser otherUser = ...

// create an entry in the Follow table


ParseObject follow = new ParseObject("Follow");
follow.put("from", ParseUser.getCurrentUser());
follow.put("to", otherUser);
follow.put("date", Date());
follow.saveInBackground();

If we want to find all of the people we are following, we


can execute a query on the Follow table:

// set up the query on the Follow table 📋


ParseQuery<ParseObject> query =
ParseQuery.getQuery("Follow");
query.whereEqualTo("from",
ParseUser.getCurrentUser());

// execute the query


query.findInBackground(newFindCallback<ParseObject
() {
public void done(List<ParseObject>
followList, ParseException e) {

}
});

It’s also pretty easy to find all the users that are
following the current user by querying on the to key:

// set up the query on the Follow table 📋


ParseQuery<ParseObject> query =
ParseQuery.getQuery("Follow");
query.whereEqualTo("to",
ParseUser.getCurrentUser());

// execute the query


query.findInBackground(newFindCallback<ParseObject
() {
public void done(List<ParseObject>
followList, ParseException e) {

}
});

USING AN ARRAY

Arrays are used in Many-to-Many relationships in much


the same way that they are for One-to-Many
relationships. All objects on one side of the relationship
will have an Array column containing several objects on
the other side of the relationship.

https://docs.parseplatform.org/android/guide/#users 122/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Suppose we have a book reading app with Book and


Author objects. The Book object will contain an Array
of Author objects (with a key named authors ). Arrays
are a great fit for this scenario because it’s highly
unlikely that a book will have more than 100 or so
authors. We will put the Array in the Book object for
this reason. After all, an author could write more than
100 books.

Here is how we save a relationship between a Book and


an Author .

// let's say we have an author 📋


ParseObject author = ...

// and let's also say we have an book


ParseObject book = ...

// add the author to the authors list for the


book
book.put("authors", author);

Because the author list is an Array, you should use the


includeKey (or include on Android) parameter when
fetching a Book so that Parse returns all the authors
when it also returns the book:

// set up our query for the Book object 📋


ParseQuery bookQuery =
ParseQuery.getQuery("Book");

// configure any constraints on your query...


// tell the query to fetch all of the Author
objects along with the Book
bookQuery.include("authors");

// execute the query


bookQuery.findInBackground(newFindCallback<ParseOb
() {
public void done(List<ParseObject>
bookList, ParseException e) {
}
});

At that point, getting all the Author objects in a given


Book is a pretty straightforward call:

ArrayList<ParseObject> authorList = 📋
book.getList("authors");

https://docs.parseplatform.org/android/guide/#users 123/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Finally, suppose you have an Author and you want to


find all the Book objects in which she appears. This is
also a pretty straightforward query with an associated
constraint:

// set up our query for the Book object 📋


ParseQuery bookQuery =
ParseQuery.getQuery("Book");

// configure any constraints on your query...


booKQuery.whereEqualTo("authors", author);

// tell the query to fetch all of the Author


objects along with the Book
bookQuery.include("authors");

// execute the query


bookQuery.findInBackground(newFindCallback<ParseOb
() {
public void done(List<ParseObject>
bookList, ParseException e) {

}
});

One-to-One
In Parse, a one-to-one relationship is great for
situations where you need to split one object into two
objects. These situations should be rare, but two
examples include:

Limiting visibility of some user data. In this


scenario, you would split the object in two, where
one portion of the object contains data that is
visible to other users, while the related object
contains data that is private to the original user
(and protected via ACLs).

Splitting up an object for size. In this scenario,


your original object size is too large, so you
decide to create a secondary object to house
extra data. It is usually better to design your data
model to avoid objects this large, rather than
splitting them up. If you can’t avoid doing so, you
can also consider storing large data in a Parse
File.

Thank you for reading this far. We apologize for the


complexity. Modeling relationships in data is a hard
subject, in general. But look on the bright side: it’s still
easier than relationships with people.

https://docs.parseplatform.org/android/guide/#users 124/171
7/26/22, 5:14 PM Android Developers Guide | Parse
Want to contribute to this doc? Edit this section.

Handling Errors
Many of the methods on ParseObject , 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
database, or when there is a network failure preventing
communication with your Parse Server. You will need to
catch and deal with these exceptions.

For more details, look at the Android API.

Want to contribute to this doc? Edit this section.

Security
As your app development progresses, you will want to
use Parse’s security features in order to safeguard data.
This document explains the ways in which you can
secure your apps.

If your app is compromised, it’s not only you as the


developer who suffers, but potentially the users of your
app as well. Continue reading for our suggestions for
sensible defaults and precautions to take before
releasing your app into the wild.

Client vs. Server


When an app first connects to Parse, it identifies itself
with an Application ID and a Client key (or REST Key, or
.NET Key, or JavaScript Key, depending on which
platform you’re using). These are not secret and by
themselves they do not secure an app. These keys are
shipped as a part of your app, and anyone can
decompile your app or proxy network traffic from their

https://docs.parseplatform.org/android/guide/#users 125/171
7/26/22, 5:14 PM Android Developers Guide | Parse

device to find your client key. This exploit is even easier


with JavaScript — one can simply “view source” in the
browser and immediately find your client key.

This is why Parse has many other security features to


help you secure your data. The client key is given out to
your users, so anything that can be done with just the
client key is doable by the general public, even
malicious hackers.

The master key, on the other hand, is definitely a


security mechanism. Using the master key allows you to
bypass all of your app’s security mechanisms, such as
class-level permissions and ACLs. Having the master
key is like having root access to your app’s servers, and
you should guard your master key with the same zeal
with which you would guard your production machines’
root password.

The overall philosophy is to limit the power of your


clients (using client keys), and to perform any sensitive
actions requiring the master key in Cloud Code. You’ll
learn how to best wield this power in the section titled
Implementing Business Logic in Cloud Code.

A final note: It is recommended to setup HTTPS and


SSL in your server, to avoid man-in-the-middle attacks,
but Parse works fine as well with non-HTTPS
connections.

Class-Level Permissions
The second level of security is at the schema and data
level. Enforcing security measures at this level will
restrict how and when client applications can access
and create data on Parse. When you first begin
developing your Parse application, all of the defaults are
set so that you can be a more productive developer. For
example:

A client application can create new classes on


Parse

A client application can add fields to classes

https://docs.parseplatform.org/android/guide/#users 126/171
7/26/22, 5:14 PM Android Developers Guide | Parse

A client application can modify or query for


objects on Parse

You can configure any of these permissions to apply to


everyone, no one, or to specific users or roles in your
app. Roles are groups that contain users or other roles,
which you can assign to an object to restrict its use. Any
permission granted to a role is also granted to any of its
children, whether they are users or other roles, enabling
you to create an access hierarchy for your apps. Each of
the Parse guides includes a detailed description of
employing Roles in your apps.

Once you are confident that you have the right classes
and relationships between classes in your app, you
should begin to lock it down by doing the following:

Almost every class that you create should have these


permissions tweaked to some degree. For classes
where every object has the same permissions, class-
level settings will be most effective. For example, one
common use case entails having a class of static data
that can be read by anyone but written by no one.

RESTRICTING CLASS CREATION

As a start, you can configure your application so that


clients cannot create new classes on Parse. This is done
by setting the key allowClientClassCreation to
false in your ParseServer configuration. See the
project Readme for an overview of Configuring your
ParseServer. Once restricted, classes may only be
created from the Data Browser or with a the
masterKey . This will prevent attackers from filling your
database with unlimited, arbitrary new classes.

CONFIGURING CLASS-LEVEL PERMISSIONS

Parse lets you specify what operations are allowed per


class. This lets you restrict the ways in which clients can
access or modify your classes. To change these
settings, go to the Data Browser, select a class, and
click the “Security” button.

You can configure the client’s ability to perform each of


the following operations for the selected class:

https://docs.parseplatform.org/android/guide/#users 127/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Read:

Get: With Get permission, users can fetch


objects in this table if they know their
objectIds.

Find: Anyone with Find permission can


query all of the objects in the table, even if
they don’t know their objectIds. Any table
with public Find permission will be
completely readable by the public, unless
you put an ACL on each object.

Write:

Update: Anyone with Update permission


can modify the fields of any object in the
table that doesn’t have an ACL. For publicly
readable data, such as game levels or
assets, you should disable this permission.

Create: Like Update, anyone with Create


permission can create new objects of a
class. As with the Update permission, you’ll
probably want to turn this off for publicly
readable data.

Delete: With this permission, people can


delete any object in the table that doesn’t
have an ACL. All they need is its objectId.

Add fields: Parse classes have schemas that are


inferred when objects are created. While you’re
developing your app, this is great, because you
can add a new field to your object without having
to make any changes on the backend. But once
you ship your app, it’s very rare to need to add
new fields to your classes automatically. You
should pretty much always turn off this
permission for all of your classes when you
submit your app to the public.

https://docs.parseplatform.org/android/guide/#users 128/171
7/26/22, 5:14 PM Android Developers Guide | Parse

For each of the above actions, you can grant permission


to all users (which is the default), or lock permissions
down to a list of roles and users. For example, a class
that should be available to all users would be set to
read-only by only enabling get and find. A logging class
could be set to write-only by only allowing creates. You
could enable moderation of user-generated content by
providing update and delete access to a particular set
of users or roles.

Object-Level Access Control


Once you’ve locked down your schema and class-level
permissions, it’s time to think about how data is
accessed by your users. Object-level access control
enables one user’s data to be kept separate from
another’s, because sometimes different objects in a
class need to be accessible by different people. For
example, a user’s private personal data should be
accessible only to them.

Parse also supports the notion of anonymous users for


those apps that want to store and protect user-specific
data without requiring explicit login.

When a user logs into an app, they initiate a session


with Parse. Through this session they can add and
modify their own data but are prevented from
modifying other users’ data.

ACCESS CONTROL LISTS

The easiest way to control who can access which data


is through access control lists, commonly known as
ACLs. The idea behind an ACL is that each object has a
list of users and roles along with what permissions that
user or role has. A user needs read permissions (or
must belong to a role that has read permissions) in
order to retrieve an object’s data, and a user needs
write permissions (or must belong to a role that has
write permissions) in order to update or delete that
object.

Once you have a User, you can start using ACLs.


Remember: Users can be created through traditional

https://docs.parseplatform.org/android/guide/#users 129/171
7/26/22, 5:14 PM Android Developers Guide | Parse

username/password sign up, through a third-party login


system like Facebook or Twitter, or even by using
Parse’s automatic anonymous users functionality. To
set an ACL on the current user’s data to not be publicly
readable, all you have to do is:

ParseUser user = ParseUser.getCurrentUser(); 📋


user.setACL(new ParseACL(user));

Most apps should do this. If you store any sensitive user


data, such as email addresses or phone numbers, you
need to set an ACL like this so that the user’s private
information isn’t visible to other users. If an object
doesn’t have an ACL, it’s readable and writeable by
everyone. The only exception is the _User class. We
never allow users to write each other’s data, but they
can read it by default. (If you as the developer need to
update other _User objects, remember that your
master key can provide the power to do this.)

To make it super easy to create user-private ACLs for


every object, we have a way to set a default ACL that
will be used for every new object you create:

ParseACL.setDefaultACL(new ParseACL(), true); 📋

If you want the user to have some data that is public


and some that is private, it’s best to have two separate
objects. You can add a pointer to the private data from
the public one.

ParseObject privateData = new 📋


ParseObject("PrivateUserData");
privateData.setACL(new
ParseACL(ParseUser.getCurrentUser()));
privateData.put("phoneNumber", "555-5309");

ParseUser.getCurrentUser().put("privateData",
privateData);

Of course, you can set different read and write


permissions on an object. For example, this is how you
would create an ACL for a public post by a user, where
anyone can read it:

https://docs.parseplatform.org/android/guide/#users 130/171
7/26/22, 5:14 PM Android Developers Guide | Parse

ParseACL acl = new ParseACL(); 📋


acl.setPublicReadAccess(true);
acl.setWriteAccess(ParseUser.getCurrentUser(),
true);

Sometimes it’s inconvenient to manage permissions on


a per-user basis, and you want to have groups of users
who get treated the same (like a set of admins with
special powers). Roles are a special kind of object that
let you create a group of users that can all be assigned
to the ACL. The best thing about roles is that you can
add and remove users from a role without having to
update every single object that is restricted to that role.
To create an object that is writeable only by admins:

// Assuming you've already created a role 📋


called "admins"...
ParseACL acl = new ParseACL();
acl.setPublicReadAccess(true);
acl.setRoleWriteAccess("admins", true);

Of course, this snippet assumes you’ve already created


a role named “admins”. This is often reasonable when
you have a small set of special roles set up while
developing your app. Roles can also be created and
updated on the fly — for example, adding new friends
to a “friendOf___” role after each connection is made.

All this is just the beginning. Applications can enforce all


sorts of complex access patterns through ACLs and
class-level permissions. For example:

For private data, read and write access can be


restricted to the owner.

For a post on a message board, the author and


members of the “Moderators” role can have
“write” access, and the general public can have
“read” access.

For logging data that will only be accessed by the


developer through the REST API using the
master key, the ACL can deny all permissions.

Data created by a privileged group of users or the


developer, like a global message of the day, can
have public read access but restrict write access
to an “Administrators” role.

https://docs.parseplatform.org/android/guide/#users 131/171
7/26/22, 5:14 PM Android Developers Guide | Parse

A message sent from one user to another can


give “read” and “write” access just to those users.

For the curious, here’s the format for an ACL that


restricts read and write permissions to the owner
(whose objectId is identified by "aSaMpLeUsErId" )
and enables other users to read the object:

{ 📋
"*": { "read":true },
"aSaMpLeUsErId": { "read" :true, "write":
true }
}

And here’s another example of the format of an ACL


that uses a Role:

{ 📋
"role:RoleName": { "read": true },
"aSaMpLeUsErId": { "read": true, "write":
true }
}

POINTER PERMISSIONS

Pointer permissions are a special type of class-level


permission that create a virtual ACL on every object in
a class, based on users stored in pointer fields on those
objects. For example, given a class with an owner field,
setting a read pointer permission on owner will make
each object in the class only readable by the user in that
object’s owner field. For a class with a sender and a
reciever field, a read pointer permission on the
receiver field and a read and write pointer permission
on the sender field will make each object in the class
readable by the user in the sender and receiver field,
and writable only by the user in the sender field.

Given that objects often already have pointers to the


user(s) that should have permissions on the object,
pointer permissions provide a simple and fast solution
for securing your app using data which is already there,

https://docs.parseplatform.org/android/guide/#users 132/171
7/26/22, 5:14 PM Android Developers Guide | Parse

that doesn’t require writing any client code or cloud


code.

Pointer permissions are like virtual ACLs. They don’t


appear in the ACL column, but if you are familiar with
how ACLs work, you can think of them like ACLs. In the
above example with the sender and receiver , each
object will act as if it has an ACL of:

{ 📋
"<SENDER_USER_ID>": {
"read": true,
"write": true
},
"<RECEIVER_USER_ID>": {
"read": true
}
}

Note that this ACL is not actually created on each


object. Any existing ACLs will not be modified when you
add or remove pointer permissions, and any user
attempting to interact with an object can only interact
with the object if both the virtual ACL created by the
pointer permissions, and the real ACL already on the
object allow the interaction. For this reason, it can
sometimes be confusing to combine pointer
permissions and ACLs, so we recommend using pointer
permissions for classes that don’t have many ACLs set.
Fortunately, it’s easy to remove pointer permissions if
you later decide to use Cloud Code or ACLs to secure
your app.

REQUIRES AUTHENTICATION PERMISSION


(REQUIRES PARSE-SERVER >= 2.3.0)

Starting version 2.3.0, parse-server introduces a new


Class Level Permission requiresAuthentication . This
CLP prevents any non authenticated user from
performing the action protected by the CLP.

For example, you want to allow your authenticated


users to find and get Announcement ’s from your

https://docs.parseplatform.org/android/guide/#users 133/171
7/26/22, 5:14 PM Android Developers Guide | Parse

application and your admin role to have all privileged,


you would set the CLP:

// POST http://my-parse- 📋
server.com/schemas/Announcement
// Set the X-Parse-Application-Id and X-Parse-
Master-Key header
// body:
{
classLevelPermissions:
{
"find": {
"requiresAuthentication": true,
"role:admin": true
},
"get": {
"requiresAuthentication": true,
"role:admin": true
},
"create": { "role:admin": true },
"update": { "role:admin": true },
"delete": { "role:admin": true }
}
}

Effects:

Non authenticated users won’t be able to do


anything.

Authenticated users (any user with a valid


sessionToken) will be able to read all the objects
in that class

Users belonging to the admin role, will be able to


perform all operations.

:warning: Note that this is in no way securing your


content, if you allow anyone to login to your server,
every client will still be able to query this object.

CLP AND ACL INTERACTION

Class-Level Permissions (CLPs) and Access Control


Lists (ACLs) are both powerful tools for securing your
app, but they don’t always interact exactly how you
might expect. They actually represent two separate
layers of security that each request has to pass through
https://docs.parseplatform.org/android/guide/#users 134/171
7/26/22, 5:14 PM Android Developers Guide | Parse

to return the correct information or make the intended


change. These layers, one at the class level, and one at
the object level, are shown below. A request must pass
through BOTH layers of checks in order to be
authorized. Note that despite acting similarly to ACLs,
Pointer Permissions are a type of class level
permission, so a request must pass the pointer
permission check in order to pass the CLP check.

CLP vs ACL Diagram

As you can see, whether a user is authorized to make a


request can become complicated when you use both
CLPs and ACLs. Let’s look at an example to get a better
sense of how CLPs and ACLs can interact. Say we have
a Photo class, with an object, photoObject . There are
2 users in our app, user1 and user2 . Now lets say we
set a Get CLP on the Photo class, disabling public Get,
but allowing user1 to perform Get. Now let’s also set
an ACL on photoObject to allow Read - which includes
GET - for only user2 .

You may expect this will allow both user1 and user2
to Get photoObject , but because the CLP layer of
authentication and the ACL layer are both in effect at
all times, it actually makes it so neither user1 nor
user2 can Get photoObject . If user1 tries to Get
photoObject , it will get through the CLP layer of
authentication, but then will be rejected because it
does not pass the ACL layer. In the same way, if user2
tries to Get photoObject , it will also be rejected at the
CLP layer of authentication.

Now lets look at example that uses Pointer


Permissions. Say we have a Post class, with an object,
myPost . There are 2 users in our app, poster , and
viewer . Lets say we add a pointer permission that
gives anyone in the Creator field of the Post class
read and write access to the object, and for the myPost
object, poster is the user in that field. There is also an
ACL on the object that gives read access to viewer .
You may expect that this will allow poster to read and
edit myPost , and viewer to read it, but viewer will be
rejected by the Pointer Permission, and poster will be

https://docs.parseplatform.org/android/guide/#users 135/171
7/26/22, 5:14 PM Android Developers Guide | Parse

rejected by the ACL, so again, neither user will be able


to access the object.

Because of the complex interaction between CLPs,


Pointer Permissions, and ACLs, we recommend being
careful when using them together. Often it can be
useful to use CLPs only to disable all permissions for a
certain request type, and then using Pointer
Permissions or ACLs for other request types. For
example, you may want to disable Delete for a Photo
class, but then put a Pointer Permission on Photo so
the user who created it can edit it, just not delete it.
Because of the especially complex way that Pointer
Permissions and ACLs interact, we usually recommend
only using one of those two types of security
mechanisms.

SECURITY EDGE CASES

There are some special classes in Parse that don’t


follow all of the same security rules as every other
class. Not all classes follow Class-Level Permissions
(CLPs) or Access Control Lists (ACLs) exactly how
they are defined, and here those exceptions are
documented. Here “normal behavior” refers to CLPs
and ACLs working normally, while any other special
behaviors are described in the footnotes.

_User _Installation
normal ignores CLP, but
Get behaviour [1, not ACL
2, 3]
Find normal master key only
behavior [3] [6]
Create normal ignores CLP
behavior [4]
Update normal ignores CLP, but
behavior [5] not ACL [7]
Delete normal master key only
behavior [5] [7]
Add normal normal behavior
Field behavior

1 Logging in, or /parse/login in the REST API,


does not respect the Get CLP on the user class.
Login works just based on username and
password, and cannot be disabled using CLPs.

https://docs.parseplatform.org/android/guide/#users 136/171
7/26/22, 5:14 PM Android Developers Guide | Parse

2 Retrieving the current user, or becoming a User


based on a session token, which are both
/parse/users/me in the REST API, do not
respect the Get CLP on the user class.

3 Read ACLs do not apply to the logged in user. For


example, if all users have ACLs with Read
disabled, then doing a find query over users will
still return the logged in user. However, if the Find
CLP is disabled, then trying to perform a find on
users will still return an error.

4 Create CLPs also apply to signing up. So disabling


Create CLPs on the user class also disables
people from signing up without the master key.

5 Users can only Update and Delete themselves.


Public CLPs for Update and Delete may still apply.
For example, if you disable public Update for the
user class, then users cannot edit themselves. But
no matter what the write ACL on a user is, that
user can still Update or Delete itself, and no other
user can Update or Delete that user. As always,
however, using the master key allows users to
update other users, independent of CLPs or
ACLs.

6 Get requests on installations follow ACLs


normally. Find requests without master key is not
allowed unless you supply the installationId
as a constraint.

7 Update requests on installations do adhere to the


ACL defined on the installation, but Delete
requests are master-key-only. For more
information about how installations work, check
out the installations section of the REST guide.

Data Integrity in Cloud Code


For most apps, care around keys, class-level
permissions, and object-level ACLs are all you need to
keep your app and your users’ data safe. Sometimes,
though, you’ll run into an edge case where they aren’t
quite enough. For everything else, there’s Cloud Code.

https://docs.parseplatform.org/android/guide/#users 137/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Cloud Code allows you to upload JavaScript to Parse’s


servers, where we will run it for you. Unlike client code
running on users’ devices that may have been tampered
with, Cloud Code is guaranteed to be the code that
you’ve written, so it can be trusted with more
responsibility.

One particularly common use case for Cloud Code is


preventing invalid data from being stored. For this sort
of situation, it’s particularly important that a malicious
client not be able to bypass the validation logic.

To create validation functions, Cloud Code allows you


to implement a beforeSave trigger for your class.
These triggers are run whenever an object is saved, and
allow you to modify the object or completely reject a
save. For example, this is how you create a Cloud Code
beforeSave trigger to make sure every user has an
email address set:

Parse.Cloud.beforeSave(Parse.User, request =>📋


{
const user = request.object;
if (!user.get("email")) {
throw "Every user must have an email
address.";
}
});

Validations can lock down your app so that only certain


values are acceptable. You can also use afterSave
validations to normalize your data (e.g. formatting all
phone numbers or currency identically). You get to
retain most of the productivity benefits of accessing
Parse data directly from your client applications, but
you can also enforce certain invariants for your data on
the fly.

Common scenarios that warrant validation include:

Making sure phone numbers have the right


format

Sanitizing data so that its format is normalized

https://docs.parseplatform.org/android/guide/#users 138/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Making sure that an email address looks like a real


email address

Requiring that every user specifies an age within


a particular range

Not letting users directly change a calculated field

Not letting users delete specific objects unless


certain conditions are met

Implementing Business Logic in Cloud


Code
While validation often makes sense in Cloud Code,
there are likely certain actions that are particularly
sensitive, and should be as carefully guarded as
possible. In these cases, you can remove permissions or
the logic from clients entirely and instead funnel all
such operations to Cloud Code functions.

When a Cloud Code function is called, it can use the


optional {useMasterKey:true} parameter to gain the
ability to modify user data. With the master key, your
Cloud Code function can override any ACLs and write
data. This means that it’ll bypass all the security
mechanisms you’ve put in place in the previous
sections.

Say you want to allow a user to “like” a Post object


without giving them full write permissions on the
object. You can do this by having the client call a Cloud
Code function instead of modifying the Post itself:

The master key should be used carefully. setting


useMasterKey to true only in the individual API
function calls that need that security override:

Parse.Cloud.define("like", async request => {📋


var post = new Parse.Object("Post");
post.id = request.params.postId;
post.increment("likes");
await post.save(null, { useMasterKey: true
})
});

https://docs.parseplatform.org/android/guide/#users 139/171
7/26/22, 5:14 PM Android Developers Guide | Parse

One very common use case for Cloud Code is sending


push notifications to particular users. In general, clients
can’t be trusted to send push notifications directly,
because they could modify the alert text, or push to
people they shouldn’t be able to. Your app’s settings will
allow you to set whether “client push” is enabled or not;
we recommend that you make sure it’s disabled.
Instead, you should write Cloud Code functions that
validate the data to be pushed and sent before sending
a push.

Parse Security Summary


Parse provides a number of ways for you to secure data
in your app. As you build your app and evaluate the
kinds of data you will be storing, you can make the
decision about which implementation to choose.

It is worth repeating that that the Parse User object is


readable by all other users by default. You will want to
set the ACL on your User object accordingly if you wish
to prevent data contained in the User object (for
example, the user’s email address) from being visible by
other users.

Most classes in your app will fall into one of a couple of


easy-to-secure categories. For fully public data, you can
use class-level permissions to lock down the table to
put publicly readable and writeable by no one. For fully
private data, you can use ACLs to make sure that only
the user who owns the data can read it. But
occasionally, you’ll run into situations where you don’t
want data that’s fully public or fully private. For
example, you may have a social app, where you have
data for a user that should be readable only to friends
whom they’ve approved. For this you’ll need to a
combination of the techniques discussed in this guide
to enable exactly the sharing rules you desire.

We hope that you’ll use these tools to do everything


you can to keep your app’s data and your users’ data
secure. Together, we can make the web a safer place.

Want to contribute to this doc? Edit this section.

https://docs.parseplatform.org/android/guide/#users 140/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Performance
As your app scales, you will want to ensure that it
performs well under increased load and usage. This
document provides guidelines on how you can optimize
your app’s performance. While you can use Parse
Server for quick prototyping and not worry about
performance, you will want to keep our performance
guidelines in mind when you’re initially designing your
app. We strongly advise that you make sure you’ve
followed all suggestions before releasing your app.

You can improve your app’s performance by looking at


the following:

Writing efficient queries.

Writing restrictive queries.

Using client-side caching.

Using Cloud Code.

Avoiding count queries.

Using efficient search techniques.

Keep in mind that not all suggestions may apply to your


app. Let’s look into each one of these in more detail.

Write Efficient Queries


Parse objects are stored in a database. A Parse query
retrieves objects that you are interested in based on
conditions you apply to the query. To avoid looking
through all the data present in a particular Parse class
for every query, the database can use an index. An index
is a sorted list of items matching a given criteria.
Indexes help because they allow the database to do an
efficient search and return matching results without
looking at all of the data. Indexes are typically smaller in
size and available in memory, resulting in faster lookups.

https://docs.parseplatform.org/android/guide/#users 141/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Indexing
You are responsible for managing your database and
maintaining indexes when using Parse Server. If your
data is not indexed, every query will have to go through
the the entire data for a class to return a query result.
On the other hand, if your data is indexed appropriately,
the number of documents scanned to return a correct
query result should be low.

The order of a query constraint’s usefulness is:

Equal to

Contained In

Less than, Less than or Equal to, Greater than,


Greater than or Equal to

Prefix string matches

Not equal to

Not contained in

Everything else

Take a look at the following query to retrieve


GameScore objects:

ParseQuery<ParseObject> query = 📋
ParseQuery.getQuery("GameScore");
query.whereEqualTo("score", 50);
query.whereContainedIn("playerName",
Arrays.asList("Jonathan Walsh", "Dario Wunsch",
"Shawn Simon"));

Creating an index query based on the score field would


yield a smaller search space in general than creating
one on the playerName field.

When examining data types, booleans have a very low


entropy and and do not make good indexes. Take the
following query constraint:

query.whereEqualTo("cheatMode", false); 📋

</div>

https://docs.parseplatform.org/android/guide/#users 142/171
7/26/22, 5:14 PM Android Developers Guide | Parse

The two possible values for "cheatMode" are true and


false . If an index was added on this field it would be of
little use because it’s likely that 50% of the records will
have to be looked at to return query results.

Data types are ranked by their expected entropy of the


value space for the key:

GeoPoints

Array

Pointer

Date

String

Number

Other

Even the best indexing strategy can be defeated by


suboptimal queries.

Efficient Query Design


Writing efficient queries means taking full advantage of
indexes. Let’s take a look at some query constraints
that negate the use of indexes:

Not Equal To

Not Contained In

Additionally, the following queries under certain


scenarios may result in slow query responses if they
can’t take advantage of indexes:

Regular Expressions

Ordered By

NOT EQUAL TO

For example, let’s say you’re tracking high scores for a


game in a GameScore class. Now say you want to
retrieve the scores for all players except a certain one.
You could create this query:

https://docs.parseplatform.org/android/guide/#users 143/171
7/26/22, 5:14 PM Android Developers Guide | Parse

ParseQuery<ParseObject> query = 📋
ParseQuery.getQuery("GameScore");
query.whereNotEqualTo("playerName", "Michael
Yabuti");
query.findInBackground(new
FindCallback<ParseObject>() {
@Override
public void done(List<ParseObject> list,
ParseException e) {
if ( e == null) {
// Retrieved scores successfully
}
}
});

This query can’t take advantage of indexes. The


database has to look at all the objects in the
"GameScore" class to satisfy the constraint and
retrieve the results. As the number of entries in the
class grows, the query takes longer to run.

Luckily, most of the time a “Not Equal To” query


condition can be rewritten as a “Contained In”
condition. Instead of querying for the absence of values,
you ask for values which match the rest of the column
values. Doing this allows the database to use an index
and your queries will be faster.

For example if the User class has a column called state


which has values “SignedUp”, “Verified”, and “Invited”,
the slow way to find all users who have used the app at
least once would be to run the query:

ParseQuery<ParseUser> query = 📋
ParseQuery.getQuery(ParseUser.class);
query.whereNotEqualTo("state", "Invited");

It would be faster to use the “Contained In” condition


when setting up the query:

query.whereContainedIn("state", 📋
Arrays.asList("SignedUp", "Verified"));

Sometimes, you may have to completely rewrite your


query. Going back to the "GameScore" example, let’s
say we were running that query to display players who
had scored higher than the given player. We could do

https://docs.parseplatform.org/android/guide/#users 144/171
7/26/22, 5:14 PM Android Developers Guide | Parse

this differently, by first getting the given player’s high


score and then using the following query:

ParseQuery<ParseObject> query = 📋
ParseQuery.getQuery("GameScore");
// Previously retrieved highScore for Michael
Yabuti
query.whereGreaterThan("score", highScore);
query.findInBackground(new
FindCallback<ParseObject>() {
@Override
public void done(List<ParseObject> list,
ParseException e) {
if (e == null) {
// Retrieved scores successfully
}
}
});

The new query you use depends on your use case. This
may sometimes mean a redesign of your data model.

NOT CONTAINED IN

Similar to “Not Equal To”, the “Not Contained In” query


constraint can’t use an index. You should try and use
the complementary “Contained In” constraint. Building
on the User example, if the state column had one more
value, “Blocked”, to represent blocked users, a slow
query to find active users would be:

ParseQuery<ParseUser> query = 📋
ParseQuery.getQuery(ParseUser.class);
query.whereNotContainedIn("state",
Arrays.asList("Invited", "Blocked"));

</div>

Using a complimentary “Contained In” query constraint


will always be faster:

query.whereContainedIn("state", 📋
Arrays.asList("SignedUp", "Verified"));

This means rewriting your queries accordingly. Your


query rewrites will depend on your schema set up. It
may mean redoing that schema.

REGULAR EXPRESSIONS

https://docs.parseplatform.org/android/guide/#users 145/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Regular expression queries should be avoided due to


performance considerations. MongoDB is not efficient
for doing partial string matching except for the special
case where you only want a prefix match. Queries that
have regular expression constraints are therefore very
expensive, especially for classes with over 100,000
records. Consider restricting how many such operations
can be run on a particular app at any given time.

You should avoid using regular expression constraints


that don’t use indexes. For example, the following query
looks for data with a given string in the "playerName"
field. The string search is case insensitive and therefore
cannot be indexed:

query.whereMatches("playerName", "Michael", 📋
"i");

The following query, while case sensitive, looks for any


occurrence of the string in the field and cannot be
indexed:

query.whereContains("playerName", "Michael"); 📋

These queries are both slow. In fact, the matches and


contains query constraints are not covered in our
querying guides on purpose and we do not recommend
using them. Depending on your use case, you should
switch to using the following constraint that uses an
index, such as:

📋
query.whereStartsWith("playerName", "Michael");

This looks for data that starts with the given string. This
query will use the backend index, so it will be faster
even for large datasets.

As a best practice, when you use regular expression


constraints, you’ll want to ensure that other constraints
in the query reduce the result set to the order of
hundreds of objects to make the query efficient. If you

https://docs.parseplatform.org/android/guide/#users 146/171
7/26/22, 5:14 PM Android Developers Guide | Parse

must use the matches or contains constraints for


legacy reasons, then use case sensitive, anchored
queries where possible, for example:

query.whereMatches("playerName", "^Michael"); 📋

Most of the use cases around using regular expressions


involve implementing search. A more performant way
of implementing search is detailed later.

Write Restrictive Queries


Writing restrictive queries allows you to return only the
data that the client needs. This is critical in a mobile
environment were data usage can be limited and
network connectivity unreliable. You also want your
mobile app to appear responsive and this is directly
affected by the objects you send back to the client. The
Querying section shows the types of constraints you
can add to your existing queries to limit the data
returned. When adding constraints, you want to pay
attention and design efficient queries.

You can use skip and limit to page through results and
load the data as is needed. The query limit is 100 by
default:

query.setLimit(10); // limit to at most 10 📋


results

If you’re issuing queries on GeoPoints, make sure you


specify a reasonable radius:

ParseQuery<ParseObject> query = 📋
ParseQuery.getQuery("Place");
query.whereWithinMiles("location",
userGeoPoint, 10.0);
query.findInBackground(new
FindCallback<ParseObject>() {
@Override
public void done(List<ParseObject> list,
ParseException e) {
if (e == null) {
// List of places within 10 miles of a
user's location
}

https://docs.parseplatform.org/android/guide/#users 147/171
7/26/22, 5:14 PM Android Developers Guide | Parse
}
});

You can further limit the fields returned by calling


select:

ParseQuery<ParseObject> query = 📋
ParseQuery.getQuery("GameScore");
query.selectKeys(Arrays.asList("score",
"playerName"));
query.findInBackground(new
FindCallback<ParseObject>() {
@Override
public void done(List<ParseObject> list,
ParseException e) {
if (e == null) {
// each of results will only have the
selected fields available.
}
}
});

Client-side Caching
For queries run from iOS and Android, you can turn on
query caching. See the iOS and Android guides for
more details. Caching queries will increase your mobile
app’s performance especially in cases where you want
to display cached data while fetching the latest data
from Parse.

Use Cloud Code


Cloud Code allows you to run custom JavaScript logic
on Parse Server instead of on the client.

You can use this to offload processing to the Parse


servers thus increasing your app’s perceived
performance. You can create hooks that run whenever
an object is saved or deleted. This is useful if you want
to validate or sanitize your data. You can also use Cloud
Code to modify related objects or kick off other
processes such as sending off a push notification.

We saw examples of limiting the data returned by


writing restrictive queries. You can also use Cloud
Functions to help limit the amount of data returned to
your app. In the following example, we use a Cloud
Function to get a movie’s average rating:

https://docs.parseplatform.org/android/guide/#users 148/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Parse.Cloud.define("averageStars", async 📋
(request) => {
const query = new Parse.Query("Review");
query.equalTo("movie",
request.params.movie);
const results = await query.find();
let sum = 0;
for (let i = 0; i < results.length; ++i) {
sum += results[i].get("stars");
}
return sum / results.length;
});

You could have ran a query on the Review class on the


client, returned only the stars field data and computed
the result on the client. As the number of reviews for a
movie increases you can see that the data being
returned to the device using this methodology also
increases. Implementing the functionality through a
Cloud Function returns the one result if successful.

As you look at optimizing your queries, you’ll find that


you may have to change the queries - sometimes even
after you’ve shipped your app to the App Store or
Google Play. The ability to change your queries without
a client update is possible if you use Cloud Functions.
Even if you have to redesign your schema, you could
make all the changes in your Cloud Functions while
keeping the client interface the same to avoid an app
update. Take the average stars Cloud Function example
from before, calling it from a client SDK would look like
this:

📋
HashMap<String, String> params = new HashMap();
params.put("movie", "The Matrix");
ParseCloud.callFunctionInBackground("averageStars"
params, new FunctionCallback<Float>() {
@Override
public void done(Float aFloat, ParseException
e) {
if (e == null) {
// ratings is 4.5
}
}
});

https://docs.parseplatform.org/android/guide/#users 149/171
7/26/22, 5:14 PM Android Developers Guide | Parse

If later on, you need to modify the underlying data


model, your client call can remain the same, as long as
you return back a number that represents the ratings
result.

Avoid Count Operations


When counting objects frequently, instead consider
storing a count variable in the database that is
incremented each time an object is added. Then, the
count can quickly be retrieved by simply retrieving the
variable stored.

Suppose you are displaying movie information in your


app and your data model consists of a Movie class and a
Review class that contains a pointer to the
corresponding movie. You might want to display the
review count for each movie on the top-level navigation
screen using a query like this:

ParseQuery<ParseObject> query = 📋
ParseQuery.getQuery("Review");
// movieId corresponds to a given movie's id
query.whereEqualTo("movie", movieId);
query.countInBackground(new CountCallback() {
@Override
public void done(int i, ParseException e) {
if ( e == null) {
// Request succeeded
}
}
});

If you run the count query for each of the UI elements,


they will not run efficiently on large data sets. One
approach to avoid using the count() operator could be
to add a field to the Movie class that represents the
review count for that movie. When saving an entry to
the Review class you could increment the
corresponding movie’s review count field. This can be
done in an afterSave handler:

Parse.Cloud.afterSave("Review", 📋
function(request) {
// Get the movie id for the Review
var movieId =
request.object.get("movie").id;
// Query the Movie represented by this review
https://docs.parseplatform.org/android/guide/#users 150/171
7/26/22, 5:14 PM Android Developers Guide | Parse

var Movie = Parse.Object.extend("Movie");


var query = new Parse.Query(Movie);
query.get(movieId).then(function(movie) {
// Increment the reviews field on the Movie
object
movie.increment("reviews");
movie.save();
}, function(error) {
throw "Got an error " + error.code + " : "
+ error.message;
});
});

Your new optimized query would not need to look at


the Review class to get the review count:

ParseQuery<ParseObject> query = 📋
ParseQuery.getQuery("Movie");
query.findInBackground(new
FindCallback<ParseObject>() {
@Override
public void done(List<ParseObject> list,
ParseException e) {
if (e == null) {
// Results include the reviews count
field
}
}
});

You could also use a separate Parse Object to keep


track of counts for each review. Whenever a review
gets added or deleted, you can increment or decrement
the counts in an afterSave or afterDelete Cloud
Code handler. The approach you choose depends on
your use case.

Implement Efficient Searches


As mentioned previously, MongoDB is not efficient for
doing partial string matching. However, this is an
important use case when implementing search
functionality that scales well in production.

Simplistic search algorithms simply scan through all the


class data and executes the query on each entry. The
key to making searches run efficiently is to minimize the
number of data that has to be examined when

https://docs.parseplatform.org/android/guide/#users 151/171
7/26/22, 5:14 PM Android Developers Guide | Parse

executing each query by using an index as we’ve


outlined earlier. You’ll need to build your data model in
a way that it’s easy for us to build an index for the data
you want to be searchable. For example, string
matching queries that don’t match an exact prefix of
the string won’t be able to use an index leading to
timeout errors as the data set grows.

Let’s walk through an example of how you could build


an efficient search. You can apply the concepts you
learn in this example to your use case. Say your app has
users making posts, and you want to be able to search
those posts for hashtags or particular keywords. You’ll
want to pre-process your posts and save the list of
hashtags and words into array fields. You can do this
processing either in your app before saving the posts,
or you can use a Cloud Code beforeSave hook to do
this on the fly:

var _ = require("underscore"); 📋
Parse.Cloud.beforeSave("Post", request => {
var post = request.object;
var toLowerCase = function(w) { return
w.toLowerCase(); };
var words = post.get("text").split(/\b/);
words = _.map(words, toLowerCase);
var stopWords = ["the", "in", "and"]
words = _.filter(words, function(w) {
return w.match(/^\w+$/) && !
_.contains(stopWords, w);
});
var hashtags = post.get("text").match(/#.+?
\b/g);
hashtags = _.map(hashtags, toLowerCase);
post.set("words", words);
post.set("hashtags", hashtags);
});

This saves your words and hashtags in array fields,


which MongoDB will store with a multi-key index. There
are some important things to notice about this. First of
all it’s converting all words to lower case so that we can
look them up with lower case queries, and get case

https://docs.parseplatform.org/android/guide/#users 152/171
7/26/22, 5:14 PM Android Developers Guide | Parse

insensitive matching. Secondly, it’s filtering out common


words like ‘the’, ‘in’, and ‘and’ which will occur in a lot of
posts, to additionally reduce useless scanning of the
index when executing the queries.

Once you’ve got the keywords set up, you can


efficiently look them up using “All” constraint on your
query:

ParseQuery<ParseObject> query = 📋
ParseQuery.getQuery("Post");
query.whereContainsAll("hashtags",
Arrays.asList("#parse", "#ftw"));
query.findInBackground(new
FindCallback<ParseObject>() {
@Override
public void done(List<ParseObject> list,
ParseException e) {
if (e == null) {
// Request succeeded
}
}
});

Limits and Other Considerations


There are some limits in place to ensure the API can
provide the data you need in a performant manner. We
may adjust these in the future. Please take a moment to
read through the following list:

Objects

We recommend against storing large pieces of


binary data like images or documents in a Parse
Object.

We recommend against creating more than 64


fields on a single Parse Object to ensure that we
can build effective indexes for your queries.

We recommend against using field names that


are longer than 1,024 characters, otherwise an
index for the field will not be created.

Queries

Queries return 100 objects by default. Use the


limit parameter to change this.

Skips and limits can only be used on the outer


query.

https://docs.parseplatform.org/android/guide/#users 153/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Constraints that collide with each other will result


in only one of the constraints being applied. An
example of this would be two equalTo
constraints over the same key with two different
values, which contradicts itself (perhaps you’re
looking for ‘contains’).

No geo-queries inside compound OR queries.

Using $exists: false is not advised.

The each query method in the JavaScript SDK


cannot be used in conjunction with queries using
geo-point constraints.

A containsAll query constraint can only take up


to 9 items in the comparison array.

Push Notifications

Delivery of notifications is a “best effort”, not


guaranteed. It is not intended to deliver data to
your app, only to notify the user that there is new
data available.

Cloud Code

The params payload that is passed to a Cloud


Function is limited to 50 MB.

Want to contribute to this doc? Edit this section.

Error Codes
The following is a list of all the error codes that can be
returned by the Parse API. You may also refer to
RFC2616 for a list of http error codes. Make sure to
check the error message for more details.

API Issues

Name Code Description

https://docs.parseplatform.org/android/guide/#users 154/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Name Code Description

Invalid login
parameters.
UserInvalidLoginParams 101 Check error
message for
more details.

The specified
object or
session
doesn’t exist
or could not
be found. Can
also indicate
that you do
ObjectNotFound 101 not have the
necessary
permissions
to read or
write this
object. Check
error
message for
more details.

There is a
problem with
the
parameters
used to
construct this
query. This
could be an
InvalidQuery 102 invalid field
name or an
invalid field
type for a
specific
constraint.
Check error
message for
more details.

Missing or
invalid
classname.
Classnames
are case-
sensitive.
InvalidClassName 103
They must
start with a
letter, and a-
zA-Z0-9_ are
the only valid
characters.

https://docs.parseplatform.org/android/guide/#users 155/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Name Code Description

An
MissingObjectId 104 unspecified
object id.

An invalid
field name.
Keys are
case-
sensitive.
They must
start with a
letter, and a-
InvalidFieldName 105 zA-Z0-9_ are
the only valid
characters.
Some field
names may
be reserved.
Check error
message for
more details.

A malformed
pointer was
used. You
would
InvalidPointer 106
typically only
see this if you
have modified
a client SDK.

Badly formed
JSON was
received
upstream.
This either
indicates you
have done
something
unusual with
modifying
how things
encode to
InvalidJSON 107 JSON, or the
network is
failing badly.
Can also
indicate an
invalid utf-8
string or use
of multiple
form encoded
values. Check
error
message for
more details.

https://docs.parseplatform.org/android/guide/#users 156/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Name Code Description

The feature
you tried to
access is only
CommandUnavailable 108 available
internally for
testing
purposes.

You must call


Parse.initialize
before using
the Parse
NotInitialized 109 library. Check
the Quick
Start guide
for your
platform.

The object is
ObjectTooLarge 116
too large.

You have
reached the
ExceededConfigParamsError 116 limit of 100
config
parameters.

An invalid
value was set
for the limit.
InvalidLimitError 117
Check error
message for
more details.

An invalid
value was set
for skip.
InvalidSkipError 118
Check error
message for
more details.

The operation
isn’t allowed
for clients
due to class-
OperationForbidden 119 level
permissions.
Check error
message for
more details.

The result
CacheMiss 120 was not found
in the cache.

https://docs.parseplatform.org/android/guide/#users 157/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Name Code Description

An invalid key
was used in a
nested
InvalidNestedKey 121 JSONObject.
Check error
message for
more details.

An invalid
InvalidACL 123 ACL was
provided.

The email
InvalidEmailAddress 125 address was
invalid.

Unique field
was given a
DuplicateValue 137
value that is
already taken.

Role’s name is
InvalidRoleName 139
invalid.

Field value is
ReservedValue 139
reserved.

You have
reached the
quota on the
number of
classes in
ExceededCollectionQuota 140 your app.
Please delete
some classes
if you need to
add a new
class.

Cloud Code
script failed.
Usually points
to a
ScriptFailed 141 JavaScript
error. Check
error
message for
more details.

https://docs.parseplatform.org/android/guide/#users 158/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Name Code Description

Cloud
function not
found. Check
that the
specified
Cloud
FunctionNotFound 141
function is
present in
your Cloud
Code script
and has been
deployed.

Background
job not found.
Check that
the specified
JobNotFound 141 job is present
in your Cloud
Code script
and has been
deployed.

Cloud Code
ValidationFailed 142 validation
failed.

Webhook
WebhookError 143
error.

Invalid image
InvalidImageData 150
data.

An unsaved
UnsavedFileError 151
file.

An invalid
InvalidPushTimeError 152 push time
was specified.

HostingError 158 Hosting error.

The provided
analytics
InvalidEventName 160
event name is
invalid.

Class is not
empty and
ClassNotEmpty 255
cannot be
dropped.

https://docs.parseplatform.org/android/guide/#users 159/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Name Code Description

App name is
AppNameInvalid 256
invalid.

The request is
MissingAPIKeyError 902 missing an
API key.

The request is
using an
InvalidAPIKeyError 903
invalid API
key.

Push related errors

Name Code Description

A field was
set to an
inconsistent
IncorrectType 111 type. Check
error
message for
more details.

Invalid
channel
name. A
channel name
is either an
empty string
(the
InvalidChannelName 112
broadcast
channel) or
contains only
a-zA-Z0-9_
characters
and starts
with a letter.

Bad
subscription
type. Check
InvalidSubscriptionType 113
error
message for
more details.

The provided
InvalidDeviceToken 114 device token
is invalid.

https://docs.parseplatform.org/android/guide/#users 160/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Name Code Description

Push is
misconfigured
in your app.
PushMisconfigured 115
Check error
message for
more details.

Can’t set
channels for a
query-
targeted
push. You can
PushWhereAndChannels 115 fix this by
moving the
channels into
your push
query
constraints.

Can’t set
device type
for a query-
targeted
push. You can
fix this by
PushWhereAndType 115
incorporating
the device
type
constraints
into your
push query.

Push is
PushMissingData 115 missing a
‘data’ field.

Non-query
push is
missing a
‘channels’
PushMissingChannels 115
field. Fix by
passing a
‘channels’ or
‘query’ field.

Client-
initiated push
is not
enabled.
ClientPushDisabled 115 Check your
Parse app’s
push
notification
settings.

https://docs.parseplatform.org/android/guide/#users 161/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Name Code Description

REST-
initiated push
is not
enabled.
RestPushDisabled 115 Check your
Parse app’s
push
notification
settings.

Client-
initiated push
ClientPushWithURI 115 cannot use
the “uri”
option.

Your push
query or data
payload is too
PushQueryOrPayloadTooLarge 115 large. Check
error
message for
more details.

Invalid
InvalidExpirationError 138 expiration
value.

A push id is
MissingPushIdError 156 missing.
Deprecated.

The device
type field is
MissingDeviceTypeError 157
missing.
Deprecated.

File related errors

Name Code Description

An invalid
filename was
used for Parse
File. A valid file
name contains
InvalidFileName 122
only a-zA-Z0-
9_. characters
and is between
1 and 128
characters.

https://docs.parseplatform.org/android/guide/#users 162/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Name Code Description

Missing content
MissingContentType 126
type.

Missing content
MissingContentLength 127
length.

Invalid content
InvalidContentLength 128
length.

File size
exceeds
FileTooLarge 129
maximum
allowed.

Error saving a
FileSaveError 130
file.

File could not


FileDeleteError 153
be deleted.

Unnamed file
FileDeleteUnnamedError 161 could not be
deleted.

Installation related errors

Name Code Description

Invalid
InvalidInstallationIdError 132 installation
id.

Invalid
InvalidDeviceTypeError 133 device
type.

Invalid
InvalidChannelsArrayError 134 channels
array value.

Required
MissingRequiredFieldError 135 field is
missing.

An
immutable
ChangedImmutableFieldError 136
field was
changed.

https://docs.parseplatform.org/android/guide/#users 163/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Purchase related errors

Name Code Description

Product
purchase
ReceiptMissing 143
receipt is
missing.

Product
purchase
InvalidPurchaseReceipt 144
receipt is
invalid.

Payment is
PaymentDisabled 145 disabled on
this device.

The
product
InvalidProductIdentifier 146
identifier is
invalid.

The
product is
ProductNotFoundInAppStore 147 not found
in the App
Store.

The Apple
server
InvalidServerResponse 148
response is
not valid.

The
product
fails to
ProductDownloadFilesystemError 149 download
due to file
system
error.

User related errors

Name Code Description

The
username
UsernameMissing 200
is missing
or empty.

https://docs.parseplatform.org/android/guide/#users 164/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Name Code Description

The
password
PasswordMissing 201
is missing
or empty.

The
username
UsernameTaken 202 has already
been
taken.

Email has
UserEmailTaken 203 already
been used.

The email
is missing,
UserEmailMissing 204 and must
be
specified.

A user with
the
UserWithEmailNotFound 205 specified
email was
not found.

A user
object
without a
SessionMissing 206 valid
session
could not
be altered.

A user can
only be
MustCreateUserThroughSignup 207 created
through
signup.

An account
being
linked is
AccountAlreadyLinked 208 already
linked to
another
user.

https://docs.parseplatform.org/android/guide/#users 165/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Name Code Description

The
device’s
session
token is no
longer
InvalidSessionToken 209 valid. The
application
should ask
the user to
log in
again.

Linked services errors

Name Code Description

A user cannot
be linked to an
account
LinkedIdMissing 250 because that
account’s id
could not be
found.

A user with a
linked (e.g.
Facebook or
Twitter)
InvalidLinkedSession 251 account has an
invalid session.
Check error
message for
more details.

Invalid auth data


InvalidGeneralAuthData 251
value used.

Anonymous id is
BadAnonymousID 251 not a valid
lowercase UUID.

The supplied
Facebook
FacebookBadToken 251 session token is
expired or
invalid.

A user with a
linked Facebook
FacebookBadID 251
account has an
invalid session.

https://docs.parseplatform.org/android/guide/#users 166/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Name Code Description

Unacceptable
FacebookWrongAppID 251 Facebook
application id.

Twitter
credential
TwitterVerificationFailed 251
verification
failed.

Submitted
Twitter id does
not match the id
TwitterWrongID 251
associated with
the submitted
access token.

Submitted
Twitter handle
does not match
TwitterWrongScreenName 251 the handle
associated with
the submitted
access token.

Twitter
credentials
could not be
TwitterConnectFailure 251 verified due to
problems
accessing the
Twitter API.

A service being
linked (e.g.
Facebook or
Twitter) is
UnsupportedService 252
unsupported.
Check error
message for
more details.

Authentication
by username
and password is
not supported
for this
UsernameSigninDisabled 252
application.
Check your
Parse app’s
authentication
settings.

https://docs.parseplatform.org/android/guide/#users 167/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Name Code Description

Anonymous
users are not
supported for
this application.
AnonymousSigninDisabled 252
Check your
Parse app’s
authentication
settings.

Authentication
by Facebook is
not supported
for this
FacebookSigninDisabled 252 application.
Check your
Parse app’s
authentication
settings.

Authentication
by Twitter is not
supported for
this application.
TwitterSigninDisabled 252
Check your
Parse app’s
authentication
settings.

An invalid
authData value
was passed.
InvalidAuthDataError 253
Check error
message for
more details.

Linking to an
external
account not
supported yet
LinkingNotSupportedError 999
with
signup_or_login.
Use update
instead.

Client-only errors

Name Code Description

The connection to the


ConnectionFailed 100
Parse servers failed.

https://docs.parseplatform.org/android/guide/#users 168/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Name Code Description

There were multiple


errors. Aggregate
errors have an “errors”
property, which is an
AggregateError 600
array of error objects
with more detail about
each error that
occurred.

Unable to read input


FileReadError 601 for a Parse File on the
client.

A real error code is


unavailable because we
had to use an
XDomainRequest
object to allow CORS
XDomainRequest 602
requests in Internet
Explorer, which strips
the body from HTTP
responses that have a
non-2XX status code.

Operational issues

Name Code Description

The request was slow and


timed out. Typically this
indicates that the request
is too expensive to run. You
may see this when a Cloud
RequestTimeout 124
function did not finish
before timing out, or when
a
Parse.Cloud.httpRequest
connection times out.

An inefficient query was


rejected by the server.
InefficientQueryError 154
Refer to the Performance
Guide and slow query log.

Other issues

Name Code Description

https://docs.parseplatform.org/android/guide/#users 169/171
7/26/22, 5:14 PM Android Developers Guide | Parse

Name Code Description

An unknown error
or an error
OtherCause -1
unrelated to Parse
occurred.

Internal server
error. No
InternalServerError 1
information
available.

The service is
ServiceUnavailable 2 currently
unavailable.

ClientDisconnected 4 Connection failure.

Want to contribute to this doc? Edit this section.

https://docs.parseplatform.org/android/guide/#users 170/171
7/26/22, 5:14 PM Android Developers Guide | Parse

https://docs.parseplatform.org/android/guide/#users 171/171

You might also like