Troubleshooting Common Issues with Parse

This guide is about troubleshooting common Parse issues that people encounter.

Common Issues

Can't construct query based on an associated object

If your data model has association pointer of other object, parse requires us to pass the associated object and not the object's id. For example, Post(ParseObject) has a Author(ParseObject) relationship and author attribute is ParsePointer. If you have author object id "yiadffao89" and want to find all posts by an author:

Exploring Parse

Parse has quirks. Several are outlined below.

Extending ParseUser

A common use case that comes up is trying to extend the ParseUser class to create your own custom ParseUser subclass. This causes issues with Parse and is not recommended. Instead you can explore using a delegate pattern like this instead:

This doesn't appear to be possible at this point as Parse Android SDK does not support this. Rather, we can use an identifier to specify what type of "Stove" a particular stove object is. Refer to this stackoverflow example for specifics.

Passing ParseObject through Intent

Often with Android development, you need to pass an object from one Activity to another. This is done using the Intent system and passing objects as extras within a bundle. Unfortunately, ParseObject does not currently implement Parcelable or Serializable. See the available workarounds here.

Leveraging Multi-level Relational Queries

In the case where we want to execute a query with Parse that resembles a complex SQL join query, the best way to achieve this is using inner queries as outlined briefly here. The key to inner queries is constructing a query on an associated object and then using whereMatchesQuery to form the outer query.

For example, if we had a Post which had associated entries each of which had a user (Post => Entry => User), we could get all posts which have at least one entry belonging to a particular user with the following multi-level query:

// Get current user objectUserme=(User)User.getCurrentUser();/* Equivalent SQL query (to help you to understand):
SELECT * FROM Post WHERE Post.entry IN
(SELECT * FROM Entry WHERE Entry.user = me) */// Build inner query (finding entries that have the associated user)ParseQuery<Entry>innerQuery=ParseQuery.getQuery(Entry.class);innerQuery.whereEqualTo("user",me);// Build outer query (finding posts with an entry that match the inner query)ParseQuery<Post>query=ParseQuery.getQuery(Post.class);query.whereMatchesQuery("entry",innerQuery);// Include entries and associated users in the resultsquery.include("entry");query.include("entry.user");// Execute the queryquery.findInBackground(newFindCallback<Post>(){@Overridepublicvoiddone(List<Post>postsList,ParseExceptione){...}});

With this mechanism of inner queries we can recreate a lot of the relational logic used with SQL databases. Note that there is also a mechanism called whereMatchesKeyInQuery which adds a constraint to the query that requires a particular key's value to match a specified value.

Minimizing Number of Queries

When using Parse, you can often have many different relations between models causing your app to send multiple queries out to get back all the required information. Whenever possible try to reduce the queries sent using "include" statements. Refer to the relational queries guide for more details. One caveat is that include only works for pointers to objects and as such does not work with many-to-many relational data with ParseRelation.

In addition, in certain situations making multiple queries to get all the required data for a screen is unavoidable. In these cases, encapsulating all the separate requests into a single object fetching java object can make data retrieval much more manageable in your activity. See this android guides issue for a rough example. The idea here is to wrap all the queries to Parse in a container object which aggregates all the data and fires a listener once all callbacks have returned.

Batch Save with Parse

In certain cases, there are many objects that need to be created and saved at once. In this case, we can use batch inserts to significantly speed up posting objects to parse with the saveAllInBackground static method on ParseObject:

List<ParseObject>objList=newArrayList<ParseObject>();// Add new ParseObject instances to the listobjList.add(...);// Save all created ParseObject instances at onceParseObject.saveAllInBackground(objList,newSaveCallback(){@Overridepublicvoiddone(ParseExceptione){if(e==null){Toast.makeText(getApplicationContext(),"saved",Toast.LENGTH_LONG).show();}else{Toast.makeText(getApplicationContext(),e.getMessage().toString(),Toast.LENGTH_LONG).show();}}});

Local Datastore and Public Read Access

It looks like public read access to data is necessary for local datastore to work. Local datastore returned no results when role-specific read access was setup.

Caching vs. Pinning

Even though it may not be apparent from Parse documentation, caching and pinning are somewhat different concepts. Mixing the two results in an exception like so:

Caused by: java.lang.IllegalStateException: Method not allowed when Pinning is enabled.
at com.parse.ParseQuery.checkPinningEnabled(ParseQuery.java:595)
at com.parse.ParseQuery.setCachePolicy(ParseQuery.java:610)

With caching, hasCachedResult() determines whether a specific result is in the cache.

But there doesn't seem to be a way to determine if a set of objects has been pinned.

Providing Complete Offline Support

Let's say you want your app to work completely offline. That is, when it launches for the first time, it checks if there is data in the local datastore. The app works with the local data if it is available, otherwise it fetches data from the remote store and pins it locally. When creating new data, the app pins it to the local datastore as well as persists it remotely (based on network availability). To refresh the local cache, the app provides a pull-to-refresh or some similar mechanism.

Fetching Data

// Fetch from local. If not found, fetch from remoteprogressBarVisible();query.fromPin(pinName);query.findInBackground(...){// inside success callbackif(noresultsfound){fetchRemoteData();}else{// add data to adapterprogressBarInvisible();}});// Fetch from remote and replace local pinfetchRemoteData(){newQuery.findInBackground(...){// inside success callback// add data to adapterprogressBarInvisible();unpinAndRepin(data);});}// Unpin old data and pin new data to local datastoreunpinAndRepin(data){ParseObject.unpinAllInBackground(pinName,data,newDeleteCallback(){...});// Add the latest results for this query to the cache.ParseObject.pinAllInBackground(pinName,data);}

Persisting Data

// First pin locallyonSubmit(data){progressBarVisible();pinDataLocally(data);}// After local pinning is successful, try to persist remotely.pinDataLocally(data){setProperACL();data.pinInBackground(data,newSaveCallback(){// inside success callbackpersistDataRemotely();progressBarInvisible();}}persistDataRemotely(data){data.saveEventually(...){...}}

Configuring Proper ACLs

Parse takes its access control lists very seriously--every object has public read and owner-only write access by default. If you have multiple users adding data to your Parse application and they are not all part of the same role, it's possible that data created by one user cannot be modified by another. The error message is rather cryptic:

exception: com.parse.ParseException: object not found for update

Note that it may not be possible to fix the ACLs manually for all rows in Parse DB and they keep reverting to their original values.

Solution

As a one-time operation, fix your ACLs.

// Define a new role with desired read and write access.finalParseACLroleACL=newParseACL();roleACL.setPublicReadAccess(true);finalParseRolerole=newParseRole("Engineer",roleACL);// Add users to this role.finalParseQuery<ParseUser>userQuery=ParseUser.getQuery();userQuery.findInBackground(newFindCallback<ParseUser>(){@Overridepublicvoiddone(List<ParseUser>parseUsers,ParseExceptione){if(e==null){for(finalParseUseruser:parseUsers){role.getUsers().add(user);}role.saveInBackground(newSaveCallback(){@Overridepublicvoiddone(ParseExceptione){if(e==null){Log.d("debug","Saved role "+role.getName());}else{Log.d("debug","Couldn't save role "+e.toString());}}});}else{Log.d("debug","Couldn't get users "+e.toString());}}});

// Set ACLs on the data based on the role.finalParseQuery<ParseObject>query=...finalParseACLroleACL=newParseACL();roleACL.setPublicReadAccess(true);roleACL.setRoleReadAccess("Engineer",true);roleACL.setRoleWriteAccess("Engineer",true);query.findInBackground(newFindCallback<ParseObject>(){@Overridepublicvoiddone(List<ParseObject>parseObjects,ParseExceptione){for(ParseObjectobj:parseObjects){finalMyObjmyObj=(MyObj)obj;myObj.setACL(roleACL);myObj.saveInBackground(...){}});

For every new object accessible by this role, be sure to set the ACL too.

Troubleshooting network issues

There are several different ways for inspecting the network traffic between Parse and your emulator or device. You can send all network calls through LogCat, or you can leverage Facebook's Stetho project to monitor the traffic using Chrome's network inspector. See also the blog posting for more details.