Grokking Android

Getting Down to the Nitty Gritty of Android Development

Android Tutorial: Using Content Providers

By

In this tutorial I am going to show you how to make use of existing content providers. It's the second part of a three part tutorial on content providers. In the first part I covered the common concepts of content providers and in the next part I am going to deal with how to write your own content provider. The first part also lists all standard content providers that are part of the Android platform. If you haven't read it yet you might want to do so now.

ContentResolver

Whenever you want to use another content provider you first have to access a ContentResolver object. This object is responsible for finding the correct content provider.

The ContentResolver decides which provider to use based on the authority part of the URI. As you will see in the third part of this mini-series a content provider must provide its authority within the manifest file. From these entries Android creates a mapping between the authorities and the corresponding ContentProvider implementations to use.

You always interact with the ContentResolver and never with ContentProvider objects themselves. This supports loose coupling and also guarantees the correct lifecycle of the content provider.

You can get the ContentResolver object by calling getContentResolver() on the Context object. The Context object should always be available since the Activity and Service classes inherit from Context and the other components also provide easy access to it.

Since you only use the class ContentResolver it has to provide all necessary CRUD-methods. Any arguments provided to the methods of the ContentResolver are passed on to the respective methods of the ContentProvider subclass.

The CRUD methods of the ContentResolver object
Method Usage
delete Deletes the object(s) for the URI provided. The URI can be item- or directory-based
insert Inserts one object. The URI must be directory-based
query Queries for all objects that fit the URI. The URI can be item- or directory-based
update Updates one or all object(s). The URI can be item- or directory-based

There are also two methods for applying multiple CRUD operations at once as shown in the next table. The more useful one is applyBatch() which I have explained in a blog post about ContentProvider batch operations.

ContentResolver methods for dealing with multiple content provider operations
Method Usage
applyBatch Allows you to execute a list of ContentProviderOperation objects. Each ContentProviderOperation object can take its own URI and type of operation
bulkInsert Allows you to insert an array of ContentValues for a directory-based URI. You can only specify one URI for all objects you want to add

Querying for data

Querying data is probably the operation you will use most often. That's true for your own providers but also for standard providers which offer some very valuable information.

For this tutorial I am going to use the UserDictionary. That's probably the easiest standard provider available and thus well suited for showing the concepts. But by default this provider has no entries, so you better add some words first. On Ice Cream Sandwich (Android 4.0) you can do so in the "Language & input" system settings. Just click on "Personal dictionaries" and add some words.

Language & input settings
Language & input settings
Adding a word to the dictionary
Adding a word to the dictionary
The arguments of the query method
Type Name Usage
URI uri The URI of the object(s) to access. This is the only argument that must not be null
String[] projection This String array indicates which columns/attributes of the objects you want to access
String selection With this argument you can determine which records to return
String[] selectionArgs The binding parameters to the previous selection argument
String sortOrder If the result should be ordered you must use this argument to determine the sort order

The return value of the query method is a Cursor object. The cursor is used to navigate between the rows of the result and to read the columns of the current row. Cursors are important resources that have to be closed whenever you have no more use for them - otherwise you keep valuable resources from being released.

The following code snippet shows how to make use of this provider. I use the CONTENT_URI of UserDictionary.Words to access the dictionary. For this example I am only interested in the IDs of the words and the words itself.


ContentResolver resolver = getContentResolver();
String[] projection = new String[]{BaseColumns._ID, UserDictionary.Words.WORD};
Cursor cursor = 
      resolver.query(UserDictionary.Words.CONTENT_URI, 
            projection, 
            null, 
            null, 
            null);
if (cursor.moveToFirst()) {
   do {
      long id = cursor.getLong(0);
      String word = cursor.getString(1);
      // do something meaningful
   } while (cursor.moveToNext());
}

There are a some more information you could read, like an id to identify the application, that added the words, or the locale of the words. For more details see the UserDictionary.Words documentation.

Inserting new records

Very often your app needs to insert data. For the built-in content providers of Android this could be because you want to add events to the Calendar provider, people to the Contacts provider, words to the UserDictionary provider and so on.

The correct content URI for inserts can only be a directory-based URI because only these represent a collection of related items.

The values to insert are specified using a ContentValues object.
This object is not much more than a collection of key/value pairs. Of course, the keys of your ContentValues object must match columns/attributes of the objects you want to update - otherwise you will get an exception. For all columns of the new object for which no key/value-pair is provided the default value is used - which most often is null.

The arguments of the insert method
Type Name Usage
URI uri The directory-based URI to which to add the object. This argument must not be null
ContentValues values The values for the object to add. This argument also must not be null

The following code snippet shows you how to add data using a content provider:


ContentValues values = new ContentValues();
values.put(Words.WORD, "Beeblebrox");
resolver.insert(UserDictionary.Words.CONTENT_URI, values);

If you want to add multiple records to the same URI you can use the bulkInsert() method. This method differs from the normal insert() method only in that it takes an array of ContentValue objects instead of just one ContentValues object. So for each record you want to add, there must be an entry within the ContentValues array. If you want to add to different URIs though - or if you want to mix insert, update and delete operations, you should use <a href="http://www.grokkingandroid.com/better-performance-with-contentprovideroperation/" title="Better Performance with ContentProviderOperation and applyBatch()">applyBatch()</a>.

Updating data

To update records you basically provide the URI, a ContentValues object and optionally also a where-clause and arguments for this where-clause.

The arguments of the update method
Type Name Usage
URI uri The URI of the object(s) to access. This argument must not be null
ContentValues values The values to substitute the current data with. This argument also must not be null
String selection With this argument you can determine which records to update
String[] selectionArgs The binding parameters to the previous selection argument

In the next snippet I am going to change the word I've just added in the previous section.


values.clear();
values.put(Words.WORD, "Zaphod");
Uri uri = ContentUris.withAppendedId(Words.CONTENT_URI, id);
long noUpdated = resolver.update(uri, values, null, null);

Here we use a ContentValues object again. The keys of your ContentValues object must of course match columns/attributes of the objects you want to update - otherwise you would get an exception. The update method changes only those columns for which keys are present in the ContentValues object.

Note the call to ContentUris.withAppendedId(). This is a helper method to create an id-based URI from a directory-based one. You use it all the time since content providers only provide constants for directory-based URIs. So whenever you want to access a specific object you should use this method.

Since I changed only one record, a URI with an appended ID is sufficient. But if you want to update multiple values, you should use the normal URI and a selection clause. You will see an example for the latter when I show you how to delete entries.

There is also the call to values.clear(). This resets the ContentValues object and thus recycles the object. This way you are reducing costly garbage collector operations.

Deleting data

The next snippet shows how to delete records. It finally deletes the word. In this code sample you can see how to use the selection and selectionArgs arguments. The array of the selectionArgs argument is used to substitute all question marks found in the selection argument.


long noDeleted = resolver.delete
      (Words.CONTENT_URI, 
      Words.WORD + " = ? ", 
      new String[]{"Zaphod"});

The delete method takes the same arguments as the update method with the exception being the values argument. Since the record is deleted anyway, substitute values are not needed.

The arguments of the delete method
Type Name Usage
URI uri The URI of the object(s) to access. This is the only argument which must not be null
String selection With this argument you can determine which records to delete
String[] selectionArgs The binding parameters to the previous selection argument

Wolfram Rittmeyer lives in Germany and has been developing with Java for many years.

He has been interested in Android for quite a while and has been blogging about all kind of topics around Android.

You can find him on Google+ and Twitter.