Parse cloud-based services for Android apps

Cloud store and query users, data objects, and files in your Android applications

Explore the advantages of storing mobile application data in a private cloud with this introduction to the Parse SDK for Android. Mobilist C. Enrique Ortiz introduces the Parse API classes for cloud-storing and manipulating users, data objects, and files for your mobile applications.

C. Enrique Ortiz, Mobile Technologist, Independent

C. Enrique Ortiz is a longtime mobilist, author, and blogger who was creating end-to-end mobile software before smartphones were smart. He has written many technical articles and two books on mobile software development, and he has helped dozens of companies with their mobile computing needs.



20 November 2012

Also available in Chinese Russian Japanese Vietnamese Portuguese Spanish

The Parse mobile SDK provides cloud-based APIs and services for iOS, Android, and Windows® applications. Parse SDK also provides a JavaScript and REST APIs. Using the Parse API, you can cloud-enable your mobile applications very quickly and with minimal effort. A mobile application that is integrated with the Parse API can easily store data objects and files on the Parse cloud, send and listen to push notifications, manage users, handle geo-location data, and use social media platforms such as Twitter and Facebook. For mobile applications that need to scale, the Parse SDK offers all the elasticity of a cloud platform.

Before you start

I assume for the purposes of this article that you are already familiar with the basic concepts required to program mobile apps with JSON, Android, and Eclipse. Before you read any further, go to Parse.com and define your application. Just follow the simple instructions starting from the sign-up page.

This article introduces the core Parse API classes for Parse users, data objects, and files. You'll learn how to work with access control lists (ACLs) and how to perform CRUD operations on data objects, and how to store and retrieve files in the Parse cloud. Examples are built on the Parse SDK for Android (see Resources).

The Parse dashboard


The Parse dashboard assists developers in managing applications. The dashboard provides general and application-specific usage metrics for APIs and file and push notifications. Application keys and settings are managed via the dashboard. The dashboard also provides a data browser that allows developers to browse and even edit stored Parse objects. The data browser is very useful for debugging. Figure 1 is a screenshot of the Parse dashboard:

Figure 1. The Parse dashboard
A screenshot of the Parse dashboard.

Applications are authenticated via an application ID and a client ID. To get your application and client IDs, you must register your application via the Parse dashboard. You will use these keys when initializing the Parse library on your app.

Parse data objects

In Parse, data is represented using ParseObject, a container of name-value pairs. ParseObject can store any data that is JSON-compatible, as shown in Listing 1:

Listing 1. A ParseObject example
ParseObject myParseObject = new ParseObject("MyObject"); // Class Name
myParseObject.put("name", "C. Enrique Ortiz");
myParseObject.put("twitterHandle", "eortiz");
myParseObject.put("followers", 123456);

ParseObject is given a classname when instantiated. The classname in Listing 1 is "MyObject." Classnames are like table names in a relational database, and Parse objects of the same class are like rows within a table.

ParseObject exposes methods similar to the ones found in a Java Map class, such as put, get, and remove, plus a number of other methods that are specific to ParseObject.

ParseObjectname keys must be alphanumeric; as a guideline, use camel-casing for name keys. Values can be of any data type that can be stored in JSON; that is, numbers, strings, booleans, arrays, JSONObject.NULL, JSONObjects, and JSONArrays. Other data types supported by ParseObject are Java Date and byte[] arrays. A ParseObject may also include other ParseObjects.

Listing 2 shows some of the supported ParseObject value data types:

Listing 2. ParseObject: Some supported value data types

// Byte Array
byte[] byteArray = {1, 2, 3, 4, 5};

// A date
Date d = new Date(); // java.util.Date

// A number
int number = 21;

// A String
String name = "Enrique";

// A JSONArray - any mix of JSONObjects, JSONArrays, Strings, Booleans, 
//   Integers, Longs, Doubles, null or NULL.
JSONArray jArray = new JSONArray();
jArray.put(number);
jArray.put(name);

// A JSONObject 
JSONObject jObject = new JSONObject();
try {
    jObject.put("number", number);
    jObject.put("name", name);
} catch (JSONException e) {
    e.printStackTrace();
}

// A ParseObject
ParseObject pObject = new ParseObject("MyObject"); // Class name
pObject.put("myByteArray", byteArray);
pObject.put("myDate", d);
pObject.put("myString", name);
pObject.put("myNumber", number);
pObject.put("myJsonArray", jArray);
pObject.put("myJsonObject", jObject);
pObject.put("myNull", JSONObject.NULL);

The code in Listing 2 creates a ParseObject which is stored as an object in the Parse cloud. Many MyObjects of the same class are then stored as rows of ParseObject data objects that can be saved, queried for, updated, and deleted from Parse's cloud storage. It's even possible to save data when the application is offline — the Parse library simply saves the data locally until a network connection has been re-established.

Modifying a ParseObject

If you are familiar with mobile application development, then you know that long operations such as network operations should typically be done in the background, on a worker thread and not on the main system UI thread. This keeps the main system thread from blocking and affecting the responsiveness of your user interface. Later in the article, I'll show you how Parse facilities work in the background to save, delete, and find objects. For now, consider the following synchronous remove() method, which is used to remove a key from the Parse object:

pObject.remove("myNumber"); // remove the field/key "myNumber" from pObject

After removing or adding fields, or updating a current field, you can save (or update) the data object on the cloud by calling one of the ParseObject's save...() methods, which I discuss later in the article.

Summary: ParseObject

ParseObject represents a data object on the Parse cloud. It provides methods to add name-value pairs, test whether a given key is present, and delete or fetch a given ParseObject from the server. ParseObject also lets you use various get...() and put...() methods to manipulate ParseObject data, merge ParseObjects, save a ParseObject in the server, and more.

Parse users, roles, and ACLs

Before I show you how to perform CRUD operations on a Parse object, you should know some things about Parse users, roles, and ACLs (access control lists). All three are very important concepts and facilities for protecting your application's data objects.

Parse users

A class called ParseUser represents a user and provides user-account functionality for Parse applications. Every Parse application has Parse users associated with it. A ParseUser is a ParseObject but with the additional properties of username, password, and email. You can add any additional data values as you see fit.

Anonymous users in Parse

An anonymous user in Parse is one without a username or password. Anonymous users are useful for mobile application functionality that doesn't require user authentication. Anonymous users can create data objects, but such objects are short-lived and won't be available once the anonymous user logs out.

Users may sign up as Parse users of your app, as shown in Listing 3:

Listing 3. ParseUser — signup
ParseUser user = new ParseUser();
user.setUsername("eortiz");
user.setPassword("123456");
user.setEmail("eortiz@nospam.com");
user.put("userType", "Author"); // add another field

// Call the asynchronous background method to sign up 
user.signUpInBackground(new SignUpCallback() {
  public void done(ParseException e) {
    if (e == null) {
      // Successful. Allow access to app.
    } else {
      // Failed....
    }
  }
});

The username and email must be unique. If a username or email is already in use, the sign-up call will fail. You should have a mechanism to notify the user if either field fails, as well as a process for trying again.

After signup, users can log in to your app, as shown in Listing 4:

Listing 4. ParseUser — log in
ParseUser.logInInBackground("eortiz", "123456", new LogInCallback() {
  public void done(ParseUser user, ParseException e) {
    if (user != null) {
      // Successful. Allow access to app.
    } else {
      // Failed
    }
  }
});

You can update user information by calling ParseUser.save(). Note, however, that only the owner of ParseUser can modify its content; for everyone else, the data is read-only.

Parse caches the currently logged-in user. You can query the current user by calling ParseUser.currentUser(). The currentUser method allows you to quickly access current user information, so that you need only prompt for credentials if the current user session is not active. Listing 5 show how to retrieve a current user in Parse:

Listing 5. ParseUser — getting the current user
ParseUser currentUser = ParseUser.getCurrentUser();
if (currentUser != null) {
  // current user is valid
} else {
  // current user not valid, ask for credentials
}

Resetting a current user

You can reset a current user in Parse by calling ParseUser.logOut(), as shown in Listing 6:

Listing 6. ParseUser — resetting current user (log out)
ParseUser.logOut(); // static method

Parse ACLs

An ACL is a list of access permissions (or controls) that are associated to a data object. The ParseACL class allows you to define the permissions for a given ParseObject. With ACLs, you can define public access to your application data objects and you can limit access to specific users or groups of users (via roles). Listing 7 demonstrates the use of Parse ACLs:

Listing 7. Using ParseACL for access permissions
// Define a Parse user
ParseUser user = new ParseUser();
user.setUsername(username);
:
:

// Define a read/write ACL
ParseACL rwACL = new ParseACL();
rwACL.setReadAccess(user, true); // allow user to do reads
rwACL.setWriteAccess(user, true); // allow user to do writes
:
:

// Define a Parse object and its ACL
ParseObject gameObject = new ParseObject("Game");
gameObject.setACL(rwACL); // allow user do read/writes on gameObject
gameObject.saveInBackground(); // save the ACL'ed object to the cloud
:
:

// You can define a public ACL that gives public access to the object
ParseACL publicACL = new ParseACL();
publicACL.setPublicReadAccess(true);
publicACL.setPublicWriteAccess(true);      
gameObject.setACL(publicACL); // allow public read/writes
gameObject.saveInBackground(); // save the ACL'ed object to the cloud

You can also define a default ACL for all newly created objects. In Listing 8 I have set the default ACL to be public for reads and writes, as defined by publicACL in Listing 7.

Listing 8. Setting default ACL
// Set a default ACL for all newly created objects as public access
ParseACL.setDefaultACL(publicACL, true);

While not shown here, we could also use the ParseRole class to grant access permissions to groups of users.

Next, we'll look at how Parse data objects are saved to and retrieved from the Parse cloud.

Parse data objects on the cloud

Once you have created and populated a ParseObject, you can save it on the Parse cloud. Saving data objects on the Parse cloud is actually one of the simplest things to do with Parse; the complexity that is usually associated with data representation, marshaling, network communication and transport, and so on, is completely hidden by Parse. You'll need to use helper methods to map your data object instance into a ParseObject and back, and you'll need to decide whether you want to dispatch the Parse save operation on your own thread or use the save-in-the-background method.

Beware of blocking the system thread!

Recall that in mobile apps, long operations such as network, file, or long computations should not be done on the main system thread. Instead, execute them under a separate worker thread. Blocking the system thread would negatively affect the app's UI responsiveness, potentially resulting in your application being forcibly closed.

ParseObject provides two kinds of save methods: save() and saveInBackground(). saveInBackground() is the recommended save method as it runs the save operation on its own worker thread. If you choose to use the synchronous save() method, be aware that it is your responsibility to call this method on its own worker thread to prevent the UI from blocking.

Listing 9 shows the code to save a Parse data object in the background:

Listing 9. Save ParseObject in the background

// ParseObject
ParseObject pObject = new ParseObject("ExampleObject");
pObject.put("myNumber", number);
pObject.put("myString", name);
pObject.saveInBackground(); // asynchronous, no callback

And Listing 10 shows the code for saving a Parse data object the background with a callback:

Listing 10. Save in the background with callback
pObject.saveInBackground(new SaveCallback () {
    @Override
    public void done(ParseException ex) {
        if (ex == null) {
            isSaved = true;
        } else {
            // Failed
            isSaved = false;
        }
    }
  });

Variations of the save...() method include the following:

  • saveAllinBackground() saves a ParseObject with or without a callback.
  • saveAll(List<ParseObject> objects) saves a list of ParseObjects.
  • saveAllinBackground(List<ParseObject> objects) saves a list of ParseObjects in the background.
  • saveEventually() lets you save a data object to the server at some point in the future; use this method if the Parse cloud is not currently accessible.


Once a ParseObject has been successfully saved on the Cloud, it is assigned a unique Object-ID. This Object-ID is very important as it uniquely identifies that ParseObject instance. You would use the Object-ID, for example, to determine if the object was successfully saved on the cloud, for retrieving and refreshing a given Parse object instance, and for deleting a particular ParseObject.

Retrieving data objects from the cloud

This section looks at methods to query and retrieve data objects stored on the Parse cloud. You can query a single ParseObject by object-ID, or you can query for one or more Parse objects using attributes. If you already have a ParseObject, you can refresh or synchronize its contents by fetching the latest values from the server. We'll look at all of these options in the following code snips.

Fetching ParseObjects

To fetch a data object from the Parse cloud, use the ParseObject method fetch() or fetchInBackground(), shown in Listing 11:

Listing 11. Fetching (unconditional)
// ParseObject
ParseObject pObject = new ParseObject("ExampleObject");
:
:

// Fetch the parse object unconditionally
try {
    pObject.fetch();
} catch (ParseException e) {
    e.printStackTrace();
}

// Fetch the parse object unconditionally, with Callback
pObject.fetchInBackground(new GetCallback() {
    @Override
    public void done(ParseObject obj, ParseException ex) {
        if (ex == null) {
            // Success
        } else {
            // Failed
        }            
    }
});

You can also fetch only if needed; for example, when fetching a Parse object that has related Parse objects, as in Listing 12:

Listing 12. Fetching as needed
ParseObject po = new ParseObject("ExampleObject");
:

ParseObject po2 = po.getParseObject("key");

// Fetch only if needed
try {
    po2.fetchIfNeeded();
} catch (ParseException e) {
    e.printStackTrace();
}

Another option is to do a fetch-if-needed operation in the background and with a callback. For this, you would use the Parse object method fetchIfNeededInBackground(GetCallback callback).

In some cases, you will need to fetch a collection of Parse objects at one time, unconditionally, or only if needed. ParseObject provides a set of static methods for this; each one takes for input a list of Parse objects and then returns a list of Parse objects:

  • fetchAll(List<ParseObject> objects)
  • fetchAllIfNeeded(List<ParseObject> objects)
  • fetchAllIfNeededInBackground(List<ParseObject> objects, FindCallback callback)
  • fetchAllInBackground(List<ParseObject> objects, FindCallback callback)

Querying data objects on the cloud

Hopefully you've seen by now that the Parse API is super comprehensive — but wait, there's more! In addition to fetching data objects, Parse also lets you query data objects using object-ID or attributes. To query a data object from the Parse cloud, use ParseQuery. You can use ParseQuery for both basic and complex data queries, receiving back a given object or a List of matching objects.

Listing 13 shows how to retrieve a specific Parse object from the server in a background thread, given an object-ID:

Listing 13. Using ParseQuery to retrieve a given ParseObject
String myID = "12345";
ParseQuery query = new ParseQuery("Players");
query.getInBackground(myID, new GetCallback() {
    @Override
    public void done(ParseObject object, ParseException e) {
        if (object != null) {
            // Get object
        } else {
            // Not found
        }
    }            
});

Note that query.getInBackground() does not use the ParseQuery cache.

Listing 14 shows a query that retrieves all data objects of class: Players. (No constraint provided.)

Listing 14. Using ParseQuery to all ParseObjects of a given class
ParseQuery query = new ParseQuery("Players");
query.findInBackground(new FindCallback() {
    @Override
    public void done(List<ParseObject> players, ParseException e) {
        if (players != null) {
            // Get list of players
        } else {
            // No players
        }
    }
});

In Listing 15 I've used a query constraint to retrieve one or more matching Parse objects; in this case active Players:

Listing 15. Using ParseQuery to retrieve matching ParseObjects


ParseQuery query = new ParseQuery("Players");
query.whereEqualTo("status", "active");
query.findInBackground(new FindCallback() {
    @Override
    public void done(List<ParseObject> players, ParseException e) {
        if (players != null) {
            // Success - players contain active players 
        } else {
            // Failed
        }
    }
});

ParseQuery also provides a method to get the count of matching objects without retrieving the objects themselves, which is very useful. Listing 16 shows how to get the count of active players:

Listing 16. Using ParseQuery to count matching ParseObjects
ParseQuery query = new ParseQuery("Players");
query.whereEqualTo("status", "active"); //remove this line to count ALL
query.countInBackground(new CountCallback() {
    @Override
    public void done(int count, ParseException e) {
        if (e == null) {
            // Success, see count variable
        } else {
            // Failed
        }
    }
});

ParseQuery methods and constraints

ParseQuery supports more than 20 different query-constraint methods; here are some examples:

  • whereMatches(String key, String regex) finds string values that match the provided regular expression.
  • whereStartsWith(String key, String prefix) finds string values that start with a provided string.
  • whereContains(String key, String substring) finds values that contain a provided string.
  • whereGreaterThan(String key, Object value) finds values that are greater than the provided value.
  • whereWithinKilometers(String key, ParseGeoPoint point, double maxDistance) finds objects with point values near the point given and within the maximum distance given.

Query results can be ordered, as shown in Listing 17. To order query results, call one of the query orderBy...() methods, specifying the field to order by.

Listing 17. Ordering query results
query.orderByAscending("name"); 

query.orderByDescending("name");



For example:



ParseQuery query = new ParseQuery("Players");
query.whereEqualTo("status", "active");
query.orderByAscending("lastName"); // By lastname ascending order
query.findInBackground(new FindCallback() {
    @Override

  public void done(List<ParseObject> players, ParseException e) {

    if (players != null) {
      // Success - players contain active players

    } else {

      // Failed
		
                          } 

             }	

});

Something else to note is that ParseQuery results are cached. You can set the query-cache policy in various ways depending on your application needs. The following cache policies are currently supported:

  • IGNORE_CACHE: Do not use the cache; this is the default cache policy.
  • CACHE_ONLY: Only load from the cache. If there are no cached results, there will be a ParseException.
  • NETWORK_ONLY: Always go to the network, but save results to the cache.
  • CACHE_ELSE_NETWORK: Hit the cache first. If that fails, load from the network. If neither cache nor network succeed, the result will be a ParseException.
  • NETWORK_ELSE_CACHE: Hit the network first. If that fails, load from the cache. If neither network nor cache succeed, the result will be a ParseException.
  • CACHE_THEN_NETWORK: Hit the cache first, then load from the network. Note that the FindCallback will actually be called twice: first with the cached results, then with the network results. This policy can only be used asynchronously with findInBackground.

Setting the cache policy is simple. Do it before calling the find...() method, as shown in Listing 18:

Listing 18. Managing the CachePolicy
ParseQuery query = new ParseQuery("Players");
query.setCachePolicy(ParseQuery.CachePolicy.NETWORK_ELSE_CACHE);
query.findInBackground(new FindCallback() {
    @Override
    public void done(List<ParseObject> players, ParseException e) {
        if (e == null) {
            // Success - players contain active players 
        } else {
            // Failed
        }
    }
});

The ParseQuery class provides all the methods needed to query for data objects that are stored on the cloud. With ParseQuery, you can specify query constraints of all kinds, count matching data objects, set limits, skip data objects, order by, clear the cache, and much more.


Removing data objects

Removing a data object from the Parse cloud is also very simple. Once you have the object instance, you can delete an existing ParseObject from the cloud by calling the ParseObject method delete() or deleteInBackground(). Both are shown in Listing 19:

Listing 19. Removing a Parse object from the cloud
parseObject.delete();
parseObject.deleteInBackground();

As you might anticipate, delete() is a blocking call, meaning that it is your responsibility to properly dispatch this call on its own worker thread. Or you can let Parse take care of threading by using deleteInBackground() with or without a callback.

Working with files

Up to this point, we have been working with Parse objects and Parse queries. I have also introduced you to Parse users, ACLs, and roles. I'll conclude with a demonstration of working with file read, write, and save functions in Parse.

Recall that you can store raw byte[] data within a ParseObject, which is fine for small amount of data. When storing larger items such as images or documents, use Parse Files.

Files in the Parse cloud are represented using ParseFile, which provides methods to get the name of a file, its URL, and the file data itself, assuming it's available. You can also download and upload files and access some other helper methods.

File data is represented by the byte[] syntax. Note that currently, a given file cannot be larger than 10MB. When naming a file, the Parse library takes care of potential name collisions. Providing a file extension helps Parse handle file content.

Listing 20 demonstrates saving a JPG file:

Listing 20. Saving a ParseFile
// Save image file
Drawable drawable = ...;
Bitmap bitmap = (Bitmap)((BitmapDrawable) drawable).getBitmap();
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
byte[] data = stream.toByteArray();                
ParseFile imageFile = new ParseFile("image.jpg", data);
imageFile.saveInBackground();

The initial statements in Listing 20 convert the bitmap into a byte[]. The byte[] is then saved using a ParseFile saveInBackground() method, similar to how a ParseObject is saved on the server.

Once the file has been saved on Parse, it must be associated with (put into) a ParseOject. In other words, Parse files are not true standalone objects and to be retrieved and used later on, it must be associated with a given ParseObject instance. This limitation might be addressed on a future release of Parse. Listing 21 associates the image file with a Player Parse object:

Listing 21. Associating a ParseFile with a ParseObject
// Associate image with Parse object
ParseObject po = new ParseObject("Players");
po.put("name", "eortiz");
po.put("photo", imageFile);
po.saveInBackground();

I have associated the file with the data object, then saved the object on the server using saveInBackgroud(), which I previously discussed.

Listing 22 shows how to retrieve a file that is associated with a data object:

Listing 22. Retrieving the ParseFile
// Retrieving the file 
ParseFile imageFile2 = (ParseFile)po.get("photo");
imageFile2.getDataInBackground(new GetDataCallback() {
  public void done(byte[] data, ParseException e) {
    if (data != null) {
      // Success; data has the file
    } else {
      // Failed
    }
  }
});

After receiving a ParseFile reference from the Parse object, I called getDataInBackground() to retrieve the ParseFile from the servers. Note that I used the callback GetDataCallback to retrieve the Parse files, as opposed to GetCallback, which is for retrieving Parse objects with ParseQuery.

In conclusion

The Parse API is very comprehensive and includes classes for accessing mobile services such as push notification, using geo-data, integrating with social media platforms, and more. In this article, I've scratched the surface of what you can do with Parse by introducing the Parse APIs for data and file cloud storage. Knowing how to store and manipulate Parse users, data objects, files, and ACLs in the Parse cloud is good foundation for further exploring this cloud platform for mobile development.

Acknowledgments

Many thanks to Athen O'Shea for his review of this article.

Resources

Learn

  • Learn more about the Parse Android SDK; also see the Parse Quick Guide to choose a mobile platform, set up an app, and download and install your Parse SDK.
  • View a complete listing of the Parse Android APIs.
  • "Develop Android applications with Eclipse (Frank Ableson, developerWorks, February 2008): Get more practice developing Android apps in the Eclipse development environment, this time using the Android Eclipse plug-in.
  • "Introduction to jQuery Mobile" (C. Enrique Ortiz, developerWorks, May 2012): Learn the basics of jQuery Mobile and how to write a functional mobile web application user interface. Working examples guide you through pages, navigation, toolbars, list views, form controls, and transition effects in jQuery Mobile.
  • "Solve your many-device-to-many-platform mobile application integration challenges" (Olivier Picciotto, developerWorks, August 2012): Mobile development and cloud computing are practically inseparable these days, but integrating mobile applications into the cloud is still new territory. Learn how the mobile enterprise application platform (MEAP) solves some mobile-to-cloud integration challenges.
  • "DevOps for mobile development" (Michael Rowe, developerWorks, July 2012): Companies all over the world want to exploit the mobile market by providing customers and users with apps that make mobile computing easier. This article thinks through some of the technical and business issues involved with integrating development and operations for mobile platforms in the workplace.
  • Follow developerWorks on Twitter.
  • Watch developerWorks on-demand demos ranging from product installation and setup demos for beginners, to advanced functionality for experienced developers.

Discuss

  • Get involved in the developerWorks community. Connect with other developerWorks users while exploring the developer-driven blogs, forums, groups, and wikis.

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology, Mobile development, Cloud computing
ArticleID=846239
ArticleTitle=Parse cloud-based services for Android apps
publish-date=11202012