Android Tutorial: Writing your own Content provider

This is the last part of a three part tutorial on content providers. In the first part I covered the common concepts of content providers. In the second part I covered how to use content providers as a client. If you haven’t read the previous posts you might want to do so now.

Implementing a content provider involves always the following steps:

  1. Create a class that extends ContentProvider
  2. Define the authority
  3. Define the URI or URIs
  4. Create constants to ease client-development
  5. Implement the getType() method
  6. Implement the CRUD-methods
  7. Add the content provider to your AndroidManifest.xml

Based on a sample project I will expand on all of these topics.

Create a class that extends ContentProvider

You start by sub-classing ContentProvider. Since ContentProvider is an abstract class you have to implement the five abstract methods. These methods are explained in detail later on, for now simply use the stubs created by Eclipse (or whatever tool you use).

The abstract methods you have to implement
Method Usage
getType(Uri) Returns the MIME type for this URI
delete(Uri uri, String selection, String[] selectionArgs) Deletes records
insert(Uri uri, ContentValues values) Adds records
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) Return records based on selection criteria
update(Uri uri, ContentValues values, String selection, String[] selectionArgs) Modifies data

The lifecycle method onCreate() is a good place to create a reference to necessary ressources – like for example a database connection. Normally content providers use a database as the underlying data store. If you do so as well, I recommend to get hold of a writeable database object inside of your onCreate() method.

public class LentItemsProvider extends ContentProvider {

   // the underlying database
   private SQLiteDatabase db = null;
   
   @Override
   public boolean onCreate() {
      this.db = new CPSampleDB(this.getContext()).getWritableDatabase();
      if (this.db == null) {
         return false;
      }
      if (this.db.isReadOnly()) {
         this.db.close();
         this.db = null;
         return false;
      }
      return true;
   }
}   

Define the Authority, URIs and constants

Up to now your code is still missing most of the functionality. But before implementing the CRUD methods you should add the authority, the URIs, an UriMatcher and constants for all attributes your provider supports as you will need all of these later on. So add the following lines:

// public constants for client development
public static final String AUTHORITY = "de.openminds.cpsample.provider.lentitems";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + LentItems.CONTENT_PATH);

// helper constants for use with the UriMatcher
private static final int LENTITEM_LIST = 1;
private static final int LENTITEM_ID = 2;
private static final UriMatcher URI_MATCHER;

/**
* Column and content type definitions for the LentItemsProvider.
*/
public static interface LentItems extends BaseColumns {
   public static final Uri CONTENT_URI = LentItemsProvider.CONTENT_URI;
   public static final String NAME = "name";
   public static final String CATEGORY = "category";
   public static final String BORROWER = "borrower";
   public static final String CONTENT_PATH = "items";
   public static final String CONTENT_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/vnd.cpsample.lentitems";
   public static final String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/vnd.cpsample.lentitems";
   public static final String[] PROJECTION_ALL = {_ID, NAME, CATEGORY, BORROWER};
   public static final String SORT_ORDER_DEFAULT = NAME + " ASC";
}

// prepare the UriMatcher
static {
   URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
   URI_MATCHER.addURI(AUTHORITY, LentItems.CONTENT_PATH, LENTITEM_LIST);
   URI_MATCHER.addURI(AUTHORITY, LentItems.CONTENT_PATH + "/#", LENTITEM_ID);
}

As explained in a previous blog post a content provider has just one authority – which also must be unique among all content providers – but might have multiple subtypes and URIs.

To deal with multiple URIs Android provides the helper class UriMatcher. This class eases the parsing of URIs. In the code sample you can see that you initialize the UriMatcher by adding a set of paths with correspondig int values. Whenever you ask if a URI matches, the UriMatcher returns the corresponding int-value to indicate which one matches. It is common practise to use constants for these int-values. The code sample shows how to add an id-based path as well as an dir-based path.

Sadly there has yet to emerge a common standard for dealing with constants for the attributes of your content provider. Even Android’s own content providers use different styles to define and structure their constants. Nevertheless I recommend to use one inner interface or class for each distinct subtype of your provider. For example for a media player you could have a class MediaPlayerProvider, and inner classes like Music, Movie, Artist and Genre. Within these inner classes you have at least the constant CONTENT_URI for the corresponding URI and I suggest to also add one constant for each and every attribute this subtype offers. In case some subtypes have common attributes you might add a separate class like BaseMediaPlayerColumns.

In the sample project I use an inner interface LentItems that defines constants for the columns and some helper constants commonly used. The interface extends BaseColumns. This interface defines the two constants _ID and _COUNT which every content provider must support.

Implement the getType() method

Every content provider must return the content type for its supported URIs. The signature of the method takes an URI and returns a String. In the next code sample I show you the getType()-method of the sample application.

@Override
public String getType(Uri uri) {
   switch (URI_MATCHER.match(uri)) {
      case LENTITEM_LIST:
         return LentItems.CONTENT_TYPE;
      case LENTITEM_ID:
         return LentItems.CONTENT_ITEM_TYPE;
      default:
         throw new IllegalArgumentException("Unsupported URI: " + uri);
   }
}

UriMatcher

The code sample shows how to make use of the UriMatcher. This pattern is repeated within each of the CRUD methods, so ĺet’s digg into it.

As mentioned the UriMatcher keeps a map of path, int-pairs. When you initialize the UriMatcher you state for each URI which int-value belongs to it. Now whenever you need to react diffently depending on the URI you use the UriMatcher. It’s match() method returns an int that you can use in a switch statement. This switch statement has case branches for the constants used during initialization. In the end a UriMatcher is just another of Androids convenience-wrappers around a map.

Adding records using insert()

As expected this method is used by your clients to insert records into your datastore. The method only accepts a dir-based URI. Thus you first have to check if the right kind of URI is passed to the insert() method. Only then can you actually insert the values into the datastore you use. The content provider API is record-based – probably since most underlying datastores are record based databases anyway. It makes implementing this method very easy, since it allows you to simply pass the ContentValues object on to the SQLiteDatabase‘s insert() method.

public Uri insert(Uri uri, ContentValues values) { 
   if (URI_MATCHER.match(uri) != LENTITEM_LIST) { 
      throw new IllegalArgumentException("Unsupported URI for insertion: " + uri); 
   } 
   long id = db.insert(DBSchema.TBL_ITEMS, null, values); 
   if (id > 0) { 
      // notify all listeners of changes and return itemUri: 
      Uri itemUri = ContentUris.withAppendedId(uri, id); 
      getContext().getContentResolver().notifyChange(itemUri, null); 
      return itemUri; 
   } 
   // s.th. went wrong: 
   throw new SQLException("Problem while inserting into " + DBSchema.TBL_ITEMS + ", uri: " + uri); // use another exception here!!!
} 

Notifying listeners of dataset changes

Clients often want to be notified about changes in the underlying datastore of your content provider. So inserting data, as well as deleting or updating data should trigger this notification.

That’s why I used the following line in the code sample above:

getContext().getContentResolver().notifyChange(uri, null); 

Of course you should only call this method when there really has been a change (say update-count is greater than zero).

Alas it is not possible to notify the client of the actual change that has occurred or to reduce the number of notifications of bulk operations. The latter operations simply call your code over and over again.

Edit: See Joel Pedraza’s comment (the gist.gisthub link) below for a solution on how to avoid multiple notifications for bulk operations.

Querying the content provider for records

Returning records from your ContentProvider is pretty easy. Android has a convenient helper class, you can use here: The SQLiteQueryBuilder. Within the query() method of a content provider this class is always used as shown below.

public Cursor query(Uri uri, String[] projection, String selection, 
               String[] selectionArgs, String sortOrder) { 
   SQLiteQueryBuilder builder = new SQLiteQueryBuilder(); 
   builder.setTables(DBSchema.TBL_ITEMS); 
   if (TextUtils.isEmpty(sortOrder)) { 
      sortOrder = DBSchema.DEFAULT_TBL_ITEMS_SORT_ORDER; 
   } 
   switch (URI_MATCHER.match(uri)) { 
      case LENTITEM_LIST: 
         // all nice and well 
         break; 
      case LENTITEM_ID: 
         // limit query to one row at most: 
         builder.appendWhere(LentItems._ID + " = " + uri.getLastPathSegment()); 
         break; 
      default: 
         throw new IllegalArgumentException("Unsupported URI: " + uri); 
   } 
   Cursor cursor = builder.query(db, projection, selection, selectionArgs, null, null, sortOrder); 
   // if we want to be notified of any changes: 
   cursor.setNotificationUri(getContext().getContentResolver(), uri); 
   return cursor; 
} 

After creating a new object you first define into which table to insert. Then you might want to add a default sort order if none is specified by the caller of your code. Next you have to add a where-clause that matches the id for id-based queries before you finally simply pass all arguments on to the builder’s query() method. This method returns a Cursor object which you simply return to your caller.

There is one thing though you have to take care of. That is the SQLiteDatabase object you have to pass to the SQLiteQueryBuilder‘s query() method. I recommend to create this object within your content providers onCreate() method and to reuse it over and over again. Others recommend getting a readable database from your SQLiteOpenHelper whenever your query() method is called. But this approach creates a new object, which is never closed properly, for every call – which I would call a bad practise.

Updating and deleting records of your content provider

The code for updating and deleting records looks pretty much the same. I’m going to show you how to update records and afterwards I will briefly describe the necessary changes for deleting.

public int update(Uri uri, ContentValues values, String selection, 
            String[] selectionArgs) { 
   int updateCount = 0; 
   switch (URI_MATCHER.match(uri)) { 
      case LENTITEM_LIST: 
         updateCount = db.update(DBSchema.TBL_ITEMS, values, selection, selectionArgs); 
         break; 
      case LENTITEM_ID: 
         String idStr = uri.getLastPathSegment(); 
         String where = LentItems._ID + " = " + idStr; 
         if (!TextUtils.isEmpty(selection)) { 
         where += " AND " + selection; 
         } 
         updateCount = db.update(DBSchema.TBL_ITEMS, values, where, selectionArgs); 
         break; 
      default: 
         throw new IllegalArgumentException("Unsupported URI: " + uri); 
   } 
   // notify all listeners of changes: 
   if (updateCount > 0) {
      getContext().getContentResolver().notifyChange(uri, null); 
   }
   return updateCount; 
} 

First you once again use your UriMatcher to distinguish between dir-based and id-based URIs. In case a dir-based URI is used you simply pass this call on to an SQLiteDatabase‘s update() method substituting the URI with the correct table-name. In case of an id-based URI you have to extract the id from the URI to form a valid where-clause. After this call SQLiteDatabase‘s update() method. Finally you return the number of modified records.

The only changes you have to make for your delete() method is to change the method-names and to get rid of the ContentValues object. Everthing else is exactly as shown in the update() method’s code sample.

@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
   int delCount = 0;
   switch (URI_MATCHER.match(uri)) {
      case LENTITEM_LIST:
         delCount = db.delete(DBSchema.TBL_ITEMS, selection, selectionArgs);
         break;
      case LENTITEM_ID:
         String idStr = uri.getLastPathSegment();
         String where = LentItems._ID + " = " + idStr;
         if (!TextUtils.isEmpty(selection)) {
            where += " AND " + selection;
         }
         delCount = db.delete(DBSchema.TBL_ITEMS, where, selectionArgs);
         break;
      default:
         throw new IllegalArgumentException("Unsupported URI: " + uri);
   }
   // notify all listeners of changes:
   if (delCount > 0) {
      getContext().getContentResolver().notifyChange(uri, null);
   }
   return delCount;
}

Lifecycle

As with all components Android also manages the creation and destruction of a content provider. But a content provider has no visible state and there is also nothing the user has entered and that should not be lost. Because of this Android can shut down the content provider whenever it sees fit.

So instead of the seven methods within the Activity-class you only have to implement one method: onCreate(). Here you should create your SQLiteOpenHelper and acquire a reference to a writeable database for later use, as shown above.

But there are two other callback-methods you might want to react to:

  1. When your content provider needs to react to changed settings (e.g. the user selected language) you have to implement the onSettingsChanged() method that takes a Configuration object as parameter.
  2. Should you keep large datasets around (which you should strive to avoid) you should also implement the onLowMemory() method to release these data.

Configuring your content provider

As with any component in Android you also have to register your content provider within the AndroidManifest.xml file. The next code sample shows a typical example configuration for a content provider.

<provider 
   android:name=".provider.LentItemsProvider"
   android:authorities="de.openminds.cpsample.provider.lentitems" 
/>

You use a <provider> element for each content provider you want to register. Of its many attributes you usually will use only authorities, name and maybe one of the permission attributes.

The name attribute simply is the fully qualified class name of your ContentProvider subclass. As always it is possible to use a simple “.” for the default-package of your application.

I have explained the rules for authorities in detail in the post about content provider clients. It should be akin to Java’s package name-conventions and must be unique among all content providers on any device it is going to be installed on. So don’t be sloppy with the provider’s authority.

I am going to explain the permission attributes in a later post.

When to use content providers

As described in the introductory post, one reason to use content providers, is to export data. So, should you use content providers only to provide data to external applications? Or when would it be appropriate to prefer them for your own application over directly accessing the database?

I recommend to use a content provider whenever your app needs to react to changes in the underlying data store – even to changes from within your app itself. And this of course is more often true than not. You might for example have a ListView which displays summary information of the data held in your data store. Now if you delete or add an entry and return to your list the list should reflect these changes. This is easily done using a content provider and its built-in notification mechanism. Using the database directly you would have to trigger a new query on your own.

The same logic applies to widgets. Your widgets should immediately reflect any changes to the data they present which is a hint that a content provider would ease your development.

Object oriented facade

You have seen that content providers provide a relational interface to data. This of course clashes with object-orientation. For this reason you might want to consider wrapping the content provider with a more object oriented interface. For an example look at Jason Wei’s blog post about an object-oriented wrapper.

The downside to this of course is that Cursor objects are commonly used in the Android library. Thus even if you follow this approach you should add a method that allows you to get the Cursor directly.

Lessons learned

With what I have described in this series you know why there are content providers in Android in the first place and when to use them. You are now able to use Android’s built-in content providers as well as create content providers of your own. Of course not all details could be covered but you have a solid basis to digg into Android’s API and to understand what is discussed on blogs, forums or on Androids mailing lists. If you liked this content you might want to subscribe to my blog. I am going to dig into content providers in more detail in follow up posts. Until then, happy coding!

Share this article:

You can leave a response, or trackback from your own site.

16 Responses to “Android Tutorial: Writing your own Content provider”

  1. ragazenta says:

    Database upgrade may take a long time, you should not call this method [getWritableDatabase] from the application main thread, including from ContentProvider.onCreate().

    CMIIW

  2. Joel Pedraza says:

    “Alas it is not possible to notify the client of the actual change that has occurred or to reduce the number of notifications of bulk operations. The latter operations simply call your code over and over again.”

    This is an implementation detail. You can build a list of uri’s that have changed and notify them all after applyBatch and bulkInsert have done their work.

  3. Animesh Sinha says:

    You have said “I recommend to use a content provider whenever your app needs to react to changes in the underlying data store”. Thats right but if I want to create library project with having content provider then it creates problem. Coz there are many application using Library Project and for all there would be same content provider having same name.

    So how to scope-up from this problem?

    • I think ContentProviders are not suitable for libraries.

      But why does your library need a ContentProvider (or any database) in the first place?

      • Animesh Sinha says:

        I have created one application which takes some user input and it stores in local database as well as to the server.

        This type of action is common in my 2-3 application so in place of adding the same code in different project I have decided to make it as a library project which all will use but my problem is with “Content Provider”, all 3 will have same content provider and it restrict to install all the three application if one is installed.

  4. Fodé says:

    Thanks you Mister Rittmeyer. Your serie of articles about Content Provider help me a lot. ;)

  5. Relawda says:

    thank you the tutorial is nice and advanced, but can you post the post the source code to download it?

  6. Mohammed LAMZIRA says:

    Thank you very much Mr Rittmeyer for this great post, however I do have some questions, in my case, I have several tables in my database, should I create a content provider for each table, or just one ? and if I create only a single content provider, how do I handle the CONTENT_URI thing ?

    • Since a database contains stuff that belongs together in one way or another the normal way to deal with multiple tables is to use one ContentProvider for a database – no matter if it has one (highly unlikely) or multiple tables.

      With multiple tables you have still one AUTHORITY but multiple CONTENT_URI constants. I suggest to create one interface per table – see the LentItems interface in the blog post. Each interface then has its own CONTENT_URI constant. That’s roughly what ContactsContract or CalendarContract are doing.

      Do not forget to add those Uris in your static block that adds all the relevant Uris to the UriMatcher instance you are using.

Leave a Reply

You can also subscribe without commenting.

Subscribe to RSS Feed My G+-Profile Follow me on Twitter!