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.
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.
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.
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
.
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.
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.
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 |