Troubleshooting Common Issues with Parse - meswapnilwagh/android_guides GitHub Wiki
This guide is about troubleshooting common Parse issues that people encounter.
Can't save object, I get "com.parse.ParseException: object not found for update"
This is usually an ACL Error which can be fixed by reviewing this post, this one and also this post.
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:
new ParseQuery("Post")
.whereEqualTo("author", ParseObject.createWithoutData(Author.class, "yiadffao89 "))
.find();
Compound equality constraints are seemingly ignored!
Rather than checking for multiple equality values, we need to instead use the whereNotContainedIn
condition to include all equality conditions within a single compound condition.
// FAILS
ParseQuery = ParseQuery.getQuery(Sample.class);
query.whereNotEqualTo("key", "valueA");
query.whereNotEqualTo("key", "valueB");
// WORKS
query.whereNotContainedIn("key", ImmutableList.of("valueA", "valueB"));
Parse has quirks. Several are outlined below.
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:
public class CustomUser {
private ParseUser user;
void CustomUser(ParseUser obj) {
user = obj;
}
void setSomeField(int someField) {
user.put("some_field", 56);
user.saveInBackground();
}
Date getCreatedAt() {
return user.getCreatedAt();
}
}
While this isn't as convenient, it works more reliably.
In certain cases, you may find yourself subclassing a Parse subclass to try to follow a single type inheritance pattern with parse. For example:
@ParseClassName("Stove")
public class Stove extends ParseObject {
// ...
}
@ParseClassName("ElectricStove")
public class ElectricStove extends Stove {
public ElectricStove() {
}
public ElectricStove(String url, String brandName) {
super(url, brandName);
}
// ...
}
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.
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.
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 object
User me = (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 results
query.include("entry");
query.include("entry.user");
// Execute the query
query.findInBackground(new FindCallback<Post>() {
@Override
public void done(List<Post> postsList, ParseException e) {
...
}
});
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.
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. See this parse support post for further clarification.
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.
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 = new ArrayList<ParseObject>();
// Add new ParseObject instances to the list
objList.add(...);
// Save all created ParseObject instances at once
ParseObject.saveAllInBackground(objList, new SaveCallback() {
@Override
public void done(ParseException e) {
if (e == null) {
Toast.makeText(getApplicationContext(), "saved", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(getApplicationContext(), e.getMessage().toString(), Toast.LENGTH_LONG).show();
}
}
});
Avoid ever calling save on multiple ParseObject
instances in quick succession.
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.
Even though it may not be apparent from Parse documentation, caching and pinning are 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.
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.
Note: The default Parse functionality removes the locally pinned data after syncing local changes.
// Fetch from local. If not found, fetch from remote
progressBarVisible();
query.fromPin(pinName);
query.findInBackground(...) {
// inside success callback
if (no results found) {
fetchRemoteData();
} else {
// add data to adapter
progressBarInvisible();
}
});
// Fetch from remote and replace local pin
fetchRemoteData() {
newQuery.findInBackground(...) {
// inside success callback
// add data to adapter
progressBarInvisible();
unpinAndRepin(data);
});
}
// Unpin old data and pin new data to local datastore
unpinAndRepin(data) {
ParseObject.unpinAllInBackground(pinName, data, new DeleteCallback() { ... });
// Add the latest results for this query to the cache.
ParseObject.pinAllInBackground(pinName, data);
}
// First pin locally
onSubmit(data) {
progressBarVisible();
pinDataLocally(data);
}
// After local pinning is successful, try to persist remotely.
pinDataLocally(data) {
setProperACL();
data.pinInBackground(data, new SaveCallback() {
// inside success callback
persistDataRemotely();
progressBarInvisible();
}
}
persistDataRemotely(data) {
data.saveEventually(...) { ... }
}
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
Review related threads here, here and also here.
Note: It may not be possible to fix the ACLs manually for all rows in Parse DB and they keep reverting to their original values.
As a one-time operation, fix your ACLs.
// Define a new role with desired read and write access.
final ParseACL roleACL = new ParseACL();
roleACL.setPublicReadAccess(true);
final ParseRole role = new ParseRole("Engineer", roleACL);
// Add users to this role.
final ParseQuery<ParseUser> userQuery = ParseUser.getQuery();
userQuery.findInBackground(new FindCallback<ParseUser>() {
@Override
public void done(List<ParseUser> parseUsers, ParseException e) {
if (e == null) {
for (final ParseUser user : parseUsers) {
role.getUsers().add(user);
}
role.saveInBackground(new SaveCallback() {
@Override
public void done(ParseException e) {
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.
final ParseQuery<ParseObject> query = ...
final ParseACL roleACL = new ParseACL();
roleACL.setPublicReadAccess(true);
roleACL.setRoleReadAccess("Engineer", true);
roleACL.setRoleWriteAccess("Engineer", true);
query.findInBackground(new FindCallback<ParseObject>() {
@Override
public void done(List<ParseObject> parseObjects, ParseException e) {
for (ParseObject obj : parseObjects) {
final MyObj myObj = (MyObj) obj;
myObj.setACL(roleACL);
myObj.saveInBackground(...) { }
});
For every new object accessible by this role, be sure to set the ACL too.
final ParseACL roleACL = new ParseACL();
roleACL.setPublicReadAccess(true);
roleACL.setRoleReadAccess("Engineer", true);
roleACL.setRoleWriteAccess("Engineer", true);
myObj.setACL(reportACL);
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.
Add this dependency to your app/build.gradle
file:
dependencies {
compile 'com.parse:parseinterceptors:0.0.2'
compile 'com.facebook.stetho:stetho:1.3.0' // Parse interceptor needs this library
}
Before you initialize Parse, add the network interceptor:
Parse.addParseNetworkInterceptor(new ParseLogInterceptor());
Parse.initialize(context, parseAppId, parseClientKey);
You can add the ParseStethoInterceptor
before initializing Parse:
Parse.addParseNetworkInterceptor(new ParseStethoInterceptor());
Parse.initialize(context, parseAppId, parseClientKey);
Open Chrome and type chrome://inspect
. You should find a list of devices that you can track. Click on the inspect
and then click on the Network
tab.