Professional Documents
Culture Documents
Guide
Browse
Getting Started
If you haven't installed the SDK yet, please head over to
the QuickStart guide to get our SDK up and running in
Android Studio. Note that we support Android 2.3 and
higher. You can also check out our API Reference for
more detailed information about our SDK.
The Parse platform provides a complete backend
solution for your mobile application. Our goal is to
totally eliminate the need for writing server code or
maintaining servers. If you're familiar with web
frameworks like Ruby on Rails, we've taken many of the
same principles and applied them to our platform. In
particular, our SDK is ready to use out of the box with
minimal conguration on your part.
On Parse, you create an App for each of your mobile
applications. Each App has its own application id and
client key that you apply to your SDK install. Your
account on Parse can accommodate multiple Apps. This
is useful even if you have one application, since you can
deploy dierent versions for test and production.
Want to contribute to this doc? Edit this section.
YES
NO
Objects
The ParseObject
Storing data on Parse is built around the ParseObject .
Each ParseObject contains key-value pairs of JSONcompatible 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:
Saving Objects
Let's say you want to save the GameScore described
above to the Parse Cloud. The interface is similar to a
Map , plus the saveInBackground method:
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 , 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
}
}
});
myObject.fetchInBackground(new
GetCallback<ParseObject>() {
public void done(ParseObject object,
ParseException e) {
if (e == null) {
// Success!
} else {
// Failure!
}
}
});
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
}
}
});
ParseObject object =
ParseObject.createWithoutData("GameScore",
"xWMyZ4YEGZ");
object.fetchFromLocalDatastoreInBackground(new
GetCallback<ParseObject>() {
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();
Updating Objects
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 the Parse Cloud.
playerName hasn't changed.
gameScore.put("score", 1338);
gameScore.put("cheatMode", true);
gameScore.saveInBackground();
}
}
});
Counters
The above example contains a common use case. The
"score" eld 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 eld. So, the same update can be rewritten as:
gameScore.increment("score");
gameScore.saveInBackground();
Arrays
To help with storing array data, there are three
operations that can be used to atomically change an
array eld:
gameScore.addAllUnique("skills",
Arrays.asList("flying", "kungfu"));
gameScore.saveInBackground();
Deleting Objects
To delete an object from the Parse Cloud:
myObject.deleteInBackground();
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:
You can also link objects using just their objectId s like
so:
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
}
});
relation.remove(post);
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.
}
}
});
ParseQuery<ParseObject> query =
relation.getQuery();
// Add other query constraints.
Data Types
So far we've used values with type String , %{number} ,
bool , 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 eld in your object:
Subclasses
Parse is designed to get you up and running as quickly
as possible. You can access all of your data using the
Into this:
SUBCLASSING PARSEOBJECT
Call
ParseObject.registerSubclass(YourClass.cla
ss) in your Application constructor before
calling Parse.initialize() . The following code
sucessfully 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,
PARSE_APPLICATION_ID, PARSE_CLIENT_KEY);
}
}
// Armor.java
@ParseClassName("Armor")
public class Armor extends ParseObject {
public String getDisplayName() {
return getString("displayName");
}
public void setDisplayName(String value) {
put("displayName", value);
}
}
INITIALIZING SUBCLASSES
Armor armorReference =
ParseObject.createWithoutData(Armor.class,
armor.getObjectId());
QUERIES
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) {
// ...
}
}
});
YES
NO
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.
Basic Queries
In many cases, getInBackground isn't powerful enough
to specify which objects you want to retrieve. The
ParseQuery oers dierent 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) {
Query Constraints
There are several ways to put constraints on the
objects found by a ParseQuery . You can lter out
objects with a particular key-value pair with
whereNotEqualTo :
query.whereNotEqualTo("playerName", "Michael
Yabuti");
query.whereNotEqualTo("playerName", "Michael
Yabuti");
query.whereGreaterThan("playerAge", 18);
ParseQuery<ParseObject> query =
ParseQuery.getQuery("GameScore");
query.whereEqualTo("playerEmail",
"dstemkoski@example.com");
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 rst results with setSkip . This can be
useful for pagination:
query.whereGreaterThanOrEqualTo("wins", 50);
query.whereDoesNotExist("score");
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
}
});
ParseQuery<ParseUser> losingUserQuery =
ParseUser.getQuery();
losingUserQuery.whereDoesNotMatchKeyInQuery("hometown"
"city", teamQuery);
losingUserQuery.findInBackground(new
FindCallback<ParseUser>() {
void done(List<ParseUser> results,
ParseException e) {
// results has the list of users with a
hometown team with a losing record
}
});
ParseQuery<ParseObject> query =
ParseQuery.getQuery("GameScore");
query.selectKeys(Arrays.asList("playerName",
"score"));;
List<ParseObject> results = query.find();
You can also search for objects where the key's array
value contains each of the values 2, 3, and 4 with the
following:
Relational Queries
There are several ways to issue queries for relational
data. If you want to retrieve objects where a eld
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
eld, you can fetch comments for a particular Post :
myPost
}
});
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.
}
});
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.
}
});
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
query.include("post.author");
query.fromLocalDatastore();
query.findInBackground(new
FindCallback<ParseObject>() {
Caching Queries
It's often useful to cache the result of a query on a
device. This lets you show data when the user's device
is oine, 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.
query.setCachePolicy(ParseQuery.CachePolicy.NETWORK_ELSE_CACHE);
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.
}
}
});
boolean isInCache =
query.hasCachedResult();
query.clearCachedResult();
ParseQuery.clearAllCachedResults();
Counting Objects
Caveat: Count queries are rate limited to a maximum of
160 requests per minute. They can also return
inaccurate results for classes with more than 1,000
objects. Thus, it is preferable to architect your
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 nd objects that match one of several
queries, you can use ParseQuery.or method to
construct a query that is an or of the queries passed in.
For instance if you want to nd 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.
}
});
YES
NO
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.
ParseUser is a subclass of the ParseObject , and has
all the same features, such as exible schema,
automatic persistence, and a key value interface. All the
methods that are on ParseObject also exist in
ParseUser . The dierence is that ParseUser has some
special additions specic to user accounts.
Properties
ParseUser has several properties that set it apart from
ParseObject :
Signing Up
The rst thing your app will do is probably ask the user
to sign up. The following code illustrates a typical sign
up:
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
ParseException to see what happened.
}
}
});
Verifying Emails
Enabling email verication in an application's settings
allows the application to reserve part of its experience
for users with conrmed email addresses. Email
verication adds the emailVerified key to the
ParseUser object. When a ParseUser 's email is set
or modied, 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
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 {
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.");
}
}
});
if
(ParseAnonymousUtils.isLinked(ParseUser.getCurrentUser()))
{
enableSignUpButton();
} else {
enableLogOutButton();
}
ParseUser.enableAutomaticUser();
ParseUser.getCurrentUser().increment("RunCount");
ParseUser.getCurrentUser().saveInBackground();
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();
}
});
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();
ParseACL.setDefaultACL(defaultACL, true);
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 o the password reset ow, ask the user for
their email address, and call:
ParseUser.requestPasswordResetInBackground("myemail@example.com"
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.
}
}
});
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.
}
}
});
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:
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.
ParseFacebookUtils.initialize(context);
@Override
protected void onActivityResult(int
requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode,
resultCode, data);
ParseFacebookUtils.onActivityResult(requestCode,
resultCode, data);
}
ParseFacebookUtils.logInWithReadPermissionsInBackground(
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!");
}
}
});
if (!ParseFacebookUtils.isLinked(user)) {
ParseFacebookUtils.linkWithReadPermissionsInBackground(user,
this, permissions, new SaveCallback() {
@Override
public void done(ParseException ex) {
if (ParseFacebookUtils.isLinked(user)) {
Log.d("MyApp", "Woohoo, user logged in
with Facebook!");
}
}
});
}
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
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.
SETUP
ParseTwitterUtils.initialize("YOUR CONSUMER
KEY", "YOUR CONSUMER SECRET");
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!");
}
}
});
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!");
}
}
});
}
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.");
}
}
});
YES
NO
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 ParseSession
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 ParseSession object for that
Installation is automatically deleted. ParseSession
objects are stored on Parse in the Session class, and
you can view them on the Parse.com Data Browser. We
provide a set of APIs to manage ParseSession objects
in your app.
Properties
.setMessage("Session is no longer
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);
}
}
});
Security
ParseSession objects can only be accessed by the
user specied in the user eld. All ParseSession
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
ParseUser.logInInBackground() , Parse will
automatically create a new unrestricted ParseSession
object in the Parse Cloud. Same for signups and
Facebook/Twitter logins.
Session objects manually created from client SDKs (by
creating an instance of ParseSession , and saving it)
are always restricted. You cannot manually create an
unrestricted sessions using the object creation API.
Restricted sessions are prohibited from creating,
modifying, or deleting any data in the ParseUser ,
ParseSession , and ParseRole classes. Restricted
session also cannot read unrestricted sessions.
Restricted Sessions are useful for "Parse for IoT"
devices (e.g Arduino or Embedded C) that may run in a
less-trusted physical environment than mobile apps.
However, please keep in mind that restricted sessions
can still read data on ParseUser , ParseSession , and
ParseRole classes, and can read/write data in any
other class just like a normal session. So it is still
important for IoT devices to be in a safe physical
environment and ideally use encrypted storage to store
the session token.
If you want to prevent restricted Sessions from
modifying classes other than ParseUser ,
ParseSession , or ParseRole , you can write a Cloud
Code beforeSave handler for that class:
Parse.Cloud.beforeSave("MyClass",
function(request, response) {
Parse.Session.current().then(function(session)
{
if (session.get('restricted')) {
response.error('write operation not
allowed');
}
response.success();
});
});
YES
NO
Roles
As your app grows in scope and user-base, you may nd
yourself needing more coarse-grained control over
access to pieces of your data than user-linked ACLs can
Properties
ParseRole has several properties that set it apart from
ParseObject :
You can add users and roles that should inherit your
new role's permissions through the "users" and "roles"
relations on ParseRole :
}
for (ParseRole childRole : rolesToAddToRole) {
role.getRoles().add(childRole);
}
role.saveInBackground();
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
YES
NO
Files
The ParseFile
ParseFile lets you store application les in the cloud
that would otherwise be too large or cumbersome to t
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
(up to 10 megabytes).
Getting started with ParseFile is easy. First, you'll
need to have the data in byte[] form and then create a
file.saveInBackground();
Smith");
jobApplication.put("applicantResumeFile",
file);
jobApplication.saveInBackground();
ParseFile applicantResume =
(ParseFile)anotherApplication.get("applicantResumeFile"
applicantResume.getDataInBackground(new
GetDataCallback() {
public void done(byte[] data, ParseException
e) {
if (e == null) {
// data has the bytes for the resume
} else {
// something went wrong
}
}
});
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:
great!".getBytes();
ParseFile file = new ParseFile("resume.txt",
data);
file.saveInBackground(new SaveCallback() {
public void done(ParseException e) {
// Handle success or failure here ...
}
}, new ProgressCallback() {
public void done(Integer percentDone) {
// Update your progress spinner here.
percentDone will be between 0 and 100.
}
});
YES
NO
GeoPoints
Parse allows you to associate real-world latitude and
ParseGeoPoint
To associate a point with an object you rst need to
create a ParseGeoPoint . For example, to create a point
with latitude of 40.0 degrees and -30.0 degrees
longitude:
placeObject.put("location", point);
Geo Queries
Now that you have a bunch of objects with spatial
coordinates, it would be nice to nd 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:
query.whereNear("location", userLocation);
query.setLimit(10);
query.findInBackground(new
FindCallback<ParseObject>() { ... });
Caveats
At the moment there are a couple of things to watch
out for:
YES
NO
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 {
@Override
public void onCreate() {
super.onCreate();
Parse.enableLocalDatastore(this);
Parse.initialize(this,
PARSE_APPLICATION_ID, PARSE_CLIENT_KEY);
}
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
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");
gameScore.put("score", 1337);
gameScore.put("playerName", "Sean Plott");
gameScore.put("cheatMode", false);
gameScore.pinInBackground();
ParseObject.pinAllInBackground(listOfObjects);
Retrieving Objects
Storing objects is great, but it's only useful if you can
then get the objects back out later. Retrieving an object
from the local datastore works just like retrieving one
over the network. The only dierence is calling the
fromLocalDatastore method to tell the ParseQuery
where to look for its results.
ParseQuery<ParseObject> query =
ParseQuery.getQuery(GameScore");
query.fromLocalDatastore();
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
Often, you'll want to nd 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 nd 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();
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.
gameScore.unpinInBackground();
ParseObject.unpinAllInBackground(listOfObjects);
ParseObject.unpinAllInBackground("MyScores");
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!
}
});
gameScore.saveEventually();
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);
}
}
});
YES
NO
Push Notications
Push notications are a great way to keep your users
engaged and informed about your app. You can reach
your entire user base quickly and eectively. This guide
will help you through the setup process and the general
usage of Parse to send push notications.
If you haven't installed the SDK yet, head over to the
Setting Up Push
If you want to start using push, start by completing the
Android Push tutorial to learn how to congure your
app. Come back to this guide afterwards to learn more
about the push features oered by Parse.
The Parse library provides push notications using
Google Cloud Messaging (GCM) if Google Play Services
are available. Learn more about Google Play Services
here.
When sending pushes to Android devices with GCM,
there are several pieces of information that Parse keeps
track of automatically:
<meta-data
android:name="com.parse.push.gcm_sender_id"
android:value="id:YOUR_SENDER_ID"
/>;
<meta-data
android:name="com.parse.push.gcm_sender_id"
android:value="id:YOUR_SENDER_ID_1,YOUR_SENDER_ID_2,YOUR_SENDER_ID_3"
/>;
Installations
Every Parse application installed on a device registered
for push notications has an associated Installation
object. The Installation object is where you store all
the data needed to target push notications. 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.
Sending Pushes
There are two ways to send push notications using
Parse: channels and advanced targeting. Channels
oer a simple and easy to use model for sending
pushes, while advanced targeting oers a more
powerful and exible model. Both are fully compatible
with each other and will be covered in this section.
Sending notications is often done from the Parse.com
push console, the REST API or from Cloud Code.
However, push notications can also be triggered by
the existing client SDKs. If you decide to send
notications from the client SDKs, you will need to set
Client Push Enabled in the Push Notications settings
of your Parse app.
However, be sure you understand that enabling Client
Push can lead to a security vulnerability in your app, as
outlined on our blog. We recommend that you enable
Client Push for testing purposes only, and move your
push notication logic into Cloud Code when your app
is ready to go into production.
You can also get the set of channels that the current
device is subscribed to using:
List<String> subscribedChannels =
ParseInstallation.getCurrentInstallation().getList(
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();
Sending Options
Push notications 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
notication as we will see in the Receiving
Notications section. An expiration date can also be
set for the notication in case it is time sensitive.
CUSTOMIZING YOUR NOTIFICATIONS
launched.
TARGETING BY PLATFORM
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();
Scheduling Pushes
Sending scheduled push notications is not currently
supported by the Android SDK. Take a look at the REST
API, JavaScript SDK or the Parse.com push console.
Receiving Pushes
Make sure you've gone through the Android Push
QuickStart to set up your app to receive pushes.
When a push notication is received, the title is
displayed in the status bar and the alert is displayed
alongside the title when the user expands the
notication 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 GCM, so if you test
your app in an emulator make sure to select an
emulator image that has Google APIs installed.
CUSTOMIZING NOTIFICATIONS
CUSTOMIZING NOTIFICATION ICONS
<meta-data
android:name="com.parse.push.notification_icon"
android:resource="@drawable/push_icon"/>
ParseAnalytics.trackAppOpened(getIntent());
Push Experiments
You can A/B test your push notications to gure out
the best way to keep your users engaged. With A/B
testing, you can simultaneously send two versions of
your push notication to dierent devices, and use each
version's push open rates to gure out which one is
better. You can test by either message or send time.
A/B TESTING
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 condence interval. It's normal for the
number of recipients in each group to be slightly
dierent 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 signicantly aect your experiment results.
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 youll be able to send localized push
messages to your users in a single broadcast.
SETUP
Troubleshooting
Setting up Push Notications 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 notication
issues. You can start by asking yourself the following
questions:
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 notications if Client
Push is enabled in your Parse app's Push Notication
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 o Client Push before
releasing their app publicly unless your use case allows
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
YES
NO
Cong
Parse Config
ParseConfig is a way to congure your applications
remotely by storing a single conguration 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 Cong
Dashboard.
ParseConfig.getInBackground(new
ConfigCallback() {
@Override
public void done(ParseConfig config,
ParseException e) {
int number =
config.getInt("winningNumber");
Log.d("TAG", String.format("Yay! The
number is %d!", number));
}
});
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 cong is always available. In the
below example we use getInBackground to retrieve
the latest version of cong from the server, and if the
fetch fails we can simply fall back to the version that we
successfully fetched before via getCurrentConfig .
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 cong from the
server every time you want to use it. You can avoid this
by simply using the cached getCurrentConfig object
and fetching the cong 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 cong
and a total size of 128KB across all parameters.
Want to contribute to this doc? Edit this section.
YES
NO
Analytics
Parse provides a number of hooks for you to get a
glimpse into the ticking heart of your app. We
understand that it's important to understand what your
app is doing, how frequently, and when.
While this section will cover dierent 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
lters to quickly access just the data you're interested
in.
ParseAnalytics.trackAppOpenedInBackground(getIntent
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.
Say your app oers search functionality for apartment
listings, and you want to track how often the feature is
used, with some additional metadata.
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);
YES
NO
User Interface
At the end of the day, users of your application will be
interacting with Android UI components. We provide
ParseLoginUI
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 congure the look and feel of the login screens
by either specifying XML congurations or constructing
an Intent in code. In this guide, we rst provide several
ways to integrate with the login library. Then, we
describe in detail how to customize the login screens.
<activity
android:name="com.parse.ui.ParseLoginActivity"
android:label="@string/app_name"
android:launchMode="singleTop">
<meta-data
android:name="com.parse.ui.ParseLoginActivity.PARSE_LOGIN_ENABLED"
android:value="true"/>
</activity>
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_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_BUTTON_TEXT : String to
display on the Facebook login button (default =
"Log in with Facebook")
<activity
android:name="com.parse.ui.ParseLoginActivity"
android:label="@string/my_app_name"
android:launchMode="singleTop">
<!-- We reference a drawable resource
here, so we must use android:resource -->
<meta-data
android:name="com.parse.ui.ParseLoginActivity.APP_LOGO"
android:resource="@drawable/my_app_logo"/>
<!-- For these non-resource options, use
android:value -->
<meta-data
android:name="com.parse.ui.ParseLoginActivity.PARSE_LOGIN_ENABLED"
android:value="true"/>
<meta-data
android:name="com.parse.ui.ParseLoginActivity.PARSE_LOGIN_EMAIL_AS_USE
android:value="true"/>
<meta-data
android:name="com.parse.ui.ParseLoginActivity.PARSE_LOGIN_HELP_TEXT"
android:value="@string/password_reset_text"/>
<meta-data
android:name="com.parse.ui.ParseLoginActivity.MIN_PASSWORD_LENGTH"
android:value="8"/>
<meta-data
android:name="com.parse.ui.ParseLoginActivity.FACEBOOK_LOGIN_ENABLED"
android:value="true"/>
<!-- We reference a string-array resource
here, so we must use android:resource -->
<meta-data
android:name="com.parse.ui.ParseLoginActivity.FACEBOOK_LOGIN_PERMISSIO
android:resource="@array/my_facebook_permissions"
</activity>
<resources>
<string-array
name="my_facebook_permissions">
<item>public_profile</item>
<item>user_friends</item>
</string-array>
</resources>
CONFIGURE BY CODE
startActivityForResult(parseLoginIntent, 0);
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 elds 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.
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
Instantiate a ParseQueryAdapter .
// 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);
}
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;
}
});
@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);
@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.
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;
}
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.
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 o, call
setPaginationEnabled(false) . With pagination
turned o, 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 rst
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.
YES
NO
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
ParseObject , 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
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-specic 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.
Importing Data
You may import data into your Parse app by using CSV
or JSON les. To create a new class with data from a
CSV or JSON le, 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 le containing regular objects could look
like:
{ "results": [
{
"score": 1337,
"playerName": "Sean Plott",
"cheatMode": false,
"createdAt": "2012-07-11T20:56:12.347Z",
"updatedAt": "2012-07-11T20:56:12.347Z",
"objectId": "fchpZwSuGG"
}]
}
{ "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/ozNo7G83Qbdk5rmyvY8l16MIK"
}]
}
YES
NO
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-tomany relationships enable complex relationships among
many objects.
There are four ways to build relationships in Parse:
One-to-Many
When youre 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
ParseQuery<ParseObject> gameQuery =
ParseQuery.getQuery("Game");
gameQuery.whereEqualTo("createdBy",
ParseUser.getCurrentUser());
ArrayList<ParseObject> weapons =
ParseUser.getCurrentUser().get("weaponsList");
Many-to-Many
Now lets 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.
ParseRelation<ParseObject> relation =
book.getRelation("authors");
relation.add(authorOne);
relation.add(authorTwo);
relation.add(authorThree);
// now save the book object
book.saveInBackground();
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);
follow.put("to", otherUser);
follow.put("date", Date());
follow.saveInBackground();
followList, ParseException e) {
}
});
USING AN ARRAY
ArrayList<ParseObject> authorList =
book.getList("authors");
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:
YES
NO
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 cloud, or
when there is a network failure preventing
communication with the Parse Cloud. You will need to
catch and deal with these exceptions.
For more details, look at the Android API.
Want to contribute to this doc? Edit this section.
YES
NO
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 suers, but potentially the users of your
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 rst begin
developing your Parse application, all of the defaults are
set so that you can be a more productive developer. For
example:
+ Read:
+ Get: With Get permission, users can fetch
objects in this table if they know their
objectIds.
+ Write:
user.setACL(new ParseACL(user));
{
"*": { "read":true },
"aSaMpLeUsErId": { "read" :true, "write":
true }
}
{
"role:RoleName": { "read": true },
"aSaMpLeUsErId": { "read": true, "write":
true }
}
POINTER PERMISSIONS
{
"<SENDER_USER_ID>": {
"read": true,
"write": true
},
"<RECEIVER_USER_ID>": {
"read": true
}
}
Get
_User
_Installation
normal behaviour
[1, 2, 3]
Find
normal behavior
[3]
Create
normal behavior
[4]
ignores CLP
Update
normal behavior
[5]
Delete
normal behavior
[5]
Add
Field
normal behavior
normal behavior
Parse.Cloud.beforeSave(Parse.User,
function(request, response) {
var user = request.object;
if (!user.get("email")) {
response.error("Every user must have an
email address.");
} else {
response.success();
}
});
Parse.Cloud.define("like", function(request,
response) {
Parse.Cloud.useMasterKey();
// Everything after this point will bypass
ACLs and other security
// even if I do things besides just updating
a Post object.
});
Parse.Cloud.define("like", function(request,
response) {
var post = new Parse.Object("Post");
post.id = request.params.postId;
post.increment("likes");
post.save(null, { useMasterKey: true
}).then(function() {
// If I choose to do something else here,
it won't be using
// the master key and I'll be subject to
ordinary security measures.
response.success();
}, function(error) {
response.error(error);
});
});
YES
NO
Performance
As your app scales, you will want to ensure that it
performs well under increased load and usage. There
are parts of optimizing performance that Parse takes
care of but there are some things you can do. This
document provides guidelines on how you can optimize
your app's performance. While you can use Parse for
Smart Indexing
The key to writing ecient queries is understanding
our indexing strategy. 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.
One of the advantages to using Parse if that you don't
have to worry about managing your own database and
maintaining indexes. We've built an abstraction to
manage all that complexity. However, you do have to
organize your data model and use performant queries
to take advantage of this. To better understand how to
go about doing this, you need to understand how our
systems are operating behind the abstraction. The key
strategy you will want to understand here is our use of
smart indexing.
Smart indexing means that we algorithmically generate
indexes for the apps that we host. The sheer number of
apps hosted on Parse means that we cannot manually
generate indexes for each app. This would not scale
well as developers can change their schemas or query
patterns at any time. This is why we rely on smart
indexes.
We perform two types of index creation logic. The rst
generates simple (single eld) indexes for each API
request, and the second does oine processing to pick
good compound indexes based on real API trac
patterns. In each case the goal is to pick indexes that
result in the smallest search space for the query, that is,
there will be less data to scan to nd results.
+ Equal to
+ Contained In
+ Less than, Less than or Equal to, Greater than,
Greater than or Equal to
ParseQuery<ParseObject> query =
ParseQuery.getQuery("GameScore");
query.whereEqualTo("score", 50);
query.whereContainedIn("playerName",
Arrays.asList("Jonathan Walsh", "Dario
Wunsch", "Shawn Simon"));
query.whereEqualTo("cheatMode", false);
+ Array
+ Pointer
+ Date
+ String
+ Number
+ Other
We score each query according to the above metrics,
and make sure we create a unique index on the three
top-scoring elds for each query. For a compound
query that consists of an OR of subqueries, we
compute the top three indexes for each subquery.
Even the best indexing strategy can be defeated by
suboptimal queries. You will need to design queries that
work hand in hand with smart indexing to deliver
performant apps.
+ 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
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
}
}
});
ParseQuery<ParseUser> query =
ParseQuery.getQuery(ParseUser.class);
query.whereNotEqualTo("state", "Invited");
query.whereContainedIn("state",
Arrays.asList("SignedUp", "Verified"));
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
ParseQuery<ParseUser> query =
ParseQuery.getQuery(ParseUser.class);
query.whereNotContainedIn("state",
Arrays.asList("Invited", "Blocked"));
query.whereContainedIn("state",
Arrays.asList("SignedUp", "Verified"));
query.whereMatches("playerName", "Michael",
"i");
query.whereContains("playerName", "Michael");
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 ecient. If you
must use the matches or contains constraints for
legacy reasons, then use case sensitive, anchored
queries where possible, for example:
query.whereMatches("playerName", "^Michael");
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
}
}
});
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.
Parse.Cloud.define("averageStars",
function(request, response) {
var Review = Parse.Object.extend("Review");
var query = new Parse.Query(Review);
query.equalTo("movie",
request.params.movie);
query.find().then(function(results) {
var sum = 0;
for (var i = 0; i < results.length; ++i) {
sum += results[i].get("stars");
}
response.success(sum / results.length);
}, function(error) {
response.error("movie lookup failed");
});
});
Parse.Cloud.run("averageStars", { "movie":
"The Matrix" }).then(function(ratings) {
// ratings is 4.5
});
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
}
}
});
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
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;
});
});
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
}
}
});
var _ = require("underscore");
Parse.Cloud.beforeSave("Post",
function(request, response) {
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
}
}
});
Files
YES
NO
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
UserInvalidLoginParams
ObjectNotFound
InvalidQuery
InvalidClassName
Code
Description
101
Invalid login
parameters. Check
error message for
more details.
101
The specied
object or session
doesn't exist or
could not be
found. Can also
indicate that you
do not have the
necessary
permissions to
read or write this
object. Check
error message for
more details.
102
There is a problem
with the
parameters used
to construct this
query. This could
be an invalid eld
name or an invalid
eld type for a
specic constraint.
Check error
message for more
details.
103
Missing or invalid
classname.
Classnames are
case-sensitive.
They must start
with a letter, and
a-zA-Z0-9_ are
the only valid
characters.
MissingObjectId
InvalidFieldName
InvalidPointer
InvalidJSON
CommandUnavailable
104
An unspecied
object id.
105
An invalid eld
name. Keys are
case-sensitive.
They must start
with a letter, and
a-zA-Z0-9_ are
the only valid
characters. Some
eld names may
be reserved. Check
error message for
more details.
106
A malformed
pointer was used.
You would
typically only see
this if you have
modied a client
SDK.
107
Badly formed
JSON was
received upstream.
This either
indicates you have
done something
unusual with
modifying how
things encode to
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.
108
109
116
116
117
An invalid value
was set for the
limit. Check error
message for more
details.
118
An invalid value
was set for skip.
Check error
message for more
details.
OperationForbidden
119
CacheMiss
120
InvalidNestedKey
121
InvalidACL
123
NotInitialized
ObjectTooLarge
ExceededConfigParamsError
InvalidLimitError
InvalidSkipError
125
DuplicateValue
137
InvalidRoleName
139
Role's name is
invalid.
ReservedValue
139
Field value is
reserved.
140
141
141
141
Background job
not found. Check
that the specied
job is present in
your Cloud Code
script and has
been deployed.
InvalidEmailAddress
ExceededCollectionQuota
ScriptFailed
FunctionNotFound
JobNotFound
success/error was
not called. A cloud
function will return
once
response.success()
or response.error()
SuccessErrorNotCalled
141
is called. A
background job
will similarly nish
execution once
status.success() or
status.error() is
called. If a function
or job never
reaches either of
the success/error
methods, this
error will be
returned. This may
happen when a
function does not
handle an error
response correctly,
preventing code
execution from
reaching the
success() method
call.
MultupleSuccessErrorCalls
141
Can't call
success/error
multiple times. A
cloud function will
return once
response.success()
or response.error()
is called. A
background job
will similarly nish
execution once
status.success() or
status.error() is
called. If a function
or job calls
success() and/or
error() more than
once in a single
execution path,
this error will be
returned.
ValidationFailed
142
Cloud Code
validation failed.
WebhookError
143
Webhook error.
InvalidImageData
150
UnsavedFileError
151
An unsaved le.
InvalidPushTimeError
152
An invalid push
time was specied.
HostingError
158
Hosting error.
160
The provided
analytics event
name is invalid.
ClassNotEmpty
255
AppNameInvalid
256
App name is
invalid.
MissingAPIKeyError
902
The request is
missing an API key.
903
The request is
using an invalid API
key.
InvalidEventName
InvalidAPIKeyError
IncorrectType
InvalidChannelName
Code
Description
111
A eld was
set to an
inconsistent
type. Check
error
message for
more details.
112
Invalid
channel
name. A
channel name
is either an
empty string
(the
broadcast
channel) or
contains only
a-zA-Z0-9_
characters
and starts
with a letter.
InvalidSubscriptionType
InvalidDeviceToken
PushMisconfigured
PushWhereAndChannels
PushWhereAndType
PushMissingData
PushMissingChannels
113
Bad
subscription
type. Check
error
message for
more details.
114
The provided
device token
is invalid.
115
Push is
miscongured
in your app.
Check error
message for
more details.
115
Can't set
channels for a
querytargeted
push. You can
x this by
moving the
channels into
your push
query
constraints.
115
Can't set
device type
for a querytargeted
push. You can
x this by
incorporating
the device
type
constraints
into your
push query.
115
Push is
missing a
'data' eld.
115
Non-query
push is
missing a
'channels'
eld. Fix by
passing a
'channels' or
'query' eld.
ClientPushDisabled
RestPushDisabled
ClientPushWithURI
PushQueryOrPayloadTooLarge
InvalidExpirationError
MissingPushIdError
MissingDeviceTypeError
115
Clientinitiated push
is not
enabled.
Check your
Parse app's
push
notication
settings.
115
RESTinitiated push
is not
enabled.
Check your
Parse app's
push
notication
settings.
115
Clientinitiated push
cannot use
the "uri"
option.
115
Your push
query or data
payload is too
large. Check
error
message for
more details.
138
Invalid
expiration
value.
156
A push id is
missing.
Deprecated.
157
The device
type eld is
missing.
Deprecated.
Code
Description
InvalidFileName
122
An invalid
lename was
used for
ParseFile. A
valid le name
contains only azA-Z0-9_.
characters and
is between 1
and 128
characters.
MissingContentType
126
Missing content
type.
MissingContentLength
127
Missing content
length.
InvalidContentLength
128
Invalid content
length.
FileTooLarge
129
File size
exceeds
maximum
allowed.
FileSaveError
130
Error saving a
le.
FileDeleteError
131
InvalidInstallationIdError
InvalidDeviceTypeError
Code
Description
132
Invalid
installation
id.
133
Invalid
device
type.
InvalidChannelsArrayError
MissingRequiredFieldError
ChangedImmutableFieldError
134
Invalid
channels
array value.
135
Required
eld is
missing.
136
An
immutable
eld was
changed.
ReceiptMissing
InvalidPurchaseReceipt
PaymentDisabled
InvalidProductIdentifier
ProductNotFoundInAppStore
InvalidServerResponse
Code
Description
143
Product
purchase
receipt is
missing.
144
Product
purchase
receipt is
invalid.
145
Payment is
disabled on
this device.
146
The
product
identier is
invalid.
147
The
product is
not found
in the App
Store.
148
The Apple
server
response is
not valid.
The
149
ProductDownloadFilesystemError
product
fails to
download
due to le
system
error.
UsernameMissing
PasswordMissing
UsernameTaken
UserEmailTaken
UserEmailMissing
UserWithEmailNotFound
SessionMissing
Code
Description
200
The
username
is missing
or empty.
201
The
password
is missing
or empty.
202
The
username
has already
been
taken.
203
Email has
already
been used.
204
The email
is missing,
and must
be
specied.
205
A user with
the
specied
email was
not found.
206
A user
object
without a
valid
session
could not
be altered.
MustCreateUserThroughSignup
AccountAlreadyLinked
InvalidSessionToken
207
A user can
only be
created
through
signup.
208
An account
being
linked is
already
linked to
another
user.
209
The
device's
session
token is no
longer
valid. The
application
should ask
the user to
log in
again.
LinkedIdMissing
InvalidLinkedSession
Code
Description
250
A user cannot
be linked to an
account
because that
account's id
could not be
found.
251
A user with a
linked (e.g.
Facebook or
Twitter)
account has an
invalid session.
Check error
message for
more details.
Invalid auth data
InvalidGeneralAuthData
BadAnonymousID
FacebookBadToken
FacebookBadID
FacebookWrongAppID
TwitterVerificationFailed
TwitterWrongID
TwitterWrongScreenName
TwitterConnectFailure
251
value used.
251
Anonymous id is
not a valid
lowercase UUID.
251
The supplied
Facebook
session token is
expired or
invalid.
251
A user with a
linked Facebook
account has an
invalid session.
251
Unacceptable
Facebook
application id.
251
Twitter
credential
verication
failed.
251
Submitted
Twitter id does
not match the id
associated with
the submitted
access token.
251
Submitted
Twitter handle
does not match
the handle
associated with
the submitted
access token.
251
Twitter
credentials
could not be
veried due to
problems
accessing the
Twitter API.
A service being
linked (e.g.
UnsupportedService
UsernameSigninDisabled
AnonymousSigninDisabled
FacebookSigninDisabled
TwitterSigninDisabled
InvalidAuthDataError
252
Facebook or
Twitter) is
unsupported.
Check error
message for
more details.
252
Authentication
by username
and password is
not supported
for this
application.
Check your
Parse app's
authentication
settings.
252
Anonymous
users are not
supported for
this application.
Check your
Parse app's
authentication
settings.
252
Authentication
by Facebook is
not supported
for this
application.
Check your
Parse app's
authentication
settings.
252
Authentication
by Twitter is not
supported for
this application.
Check your
Parse app's
authentication
settings.
253
An invalid
authData value
was passed.
Check error
message for
more details.
LinkingNotSupportedError
Linking to an
external
account not
supported yet
with
signup_or_login.
Use update
instead.
999
Client-only errors
Name
ConnectionFailed
AggregateError
FileReadError
XDomainRequest
Code
Description
100
The connection to
the Parse servers
failed.
600
601
602
Operational issues
Name
Code
Description
RequestTimeout
InefficientQueryError
RequestLimitExceeded
TemporaryRejectionError
124
154
155
159
Other issues
Name
OtherCause
Code
Description
-1
An unknown
error or an error
unrelated to
Parse occurred.
Internal server
InternalServerError
error. No
information
available.
ServiceUnavailable
The service is
currently
unavailable.
ClientDisconnected
Connection
failure.
YES
QUICKLINKS
Products
Community
Platforms
Docs
Pricing
Download
About
Status
Blog
Policies
NEED HELP?
Get Support
Developer Forum
Open Source
STAY CONNECTED
NO
Sign up
We will never, ever spam you or sell your information. Promise.