Step 1 Create a Provider Class

Just as an activity and intent receiver are both Java classes, so is a content provider. So, the big step in creating a content provider is crafting its Java class, choosing as a base class either ContentProvider or DatabaseContentProvider. Not surprisingly, DatabaseContentProvider offers some extra hooks to help with content providers using SQLite databases for storage, whereas ContentProvider is more general-purpose.

Here's how you extend these base classes to make up your content provider.

ContentProvider

If you implement a subclass of ContentProvider, you are responsible for implementing six methods that, when combined, perform the services that a content provider is supposed to offer to activities wishing to create, read, update, or delete content.

onCreate()

As with an activity, the main entry point to a content provider is onCreate(). Here, you can do whatever initialization you want. In particular, here is where you should lazy-initialize your data store. For example, if you plan on storing your data in such-and-so directory on an SD card, with an XML file serving as a "table of contents", you should check and see if that directory and XML file are there and, if not, create them so the rest of your content provider knows they are out there and available for use.

Similarly, if you have rewritten your content provider sufficiently to cause the data store to shift structure, you should check to see what structure you have now and adjust it if what you have is out of date. You don't write your own "installer" program and so have no great way of determining if, when onCreate() is called, if this is the first time ever for the content provider, the first time for a new release of a content provider that was upgraded in-place, or if this is just a normal startup.

If your content provider uses SQLite for storage, and you are not using DatabaseContentProvider, you can detect to see if your tables exist by querying on the sqlite_master table. This is useful for lazy-creating a table your content provider will need.

For example, here is the onCreate() method for Provider, from the TourIt sample application:

^Override public boolean onCreate() {

db=(new DatabaseHelper()).openDatabase(getContext(), getDbName(), null, getDbVersion());

While that doesn't seem all that special, the "magic" is in the private DatabaseHelper object:

private class DatabaseHelper extends SQLiteOpenHelper { ^Override

public void onCreate(SQLiteDatabase db) {

Cursor c=db.rawQuery("SELECT name FROM sqlite_master WHERE type='table' AND name='tours'", null);

db.execSQL("CREATE TABLE tours (_id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, desc TEXT DEFAULT '', created INTEGER, modified INTEGER, route TEXT DEFAULT '{}');");

File sdcard=new File("/sdcard/tourit");

for (File f : sdcard.listFiles()) { if (f.isDirectory()) {

long now=System.currentTimeMillis(); ContentValues map=new ContentValues();

map.put("title", f.getName()); map.put("created", now); map.put("modified", now); map.put("route", tour.getPath());

db.insert("tours", null, map);

^Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { android.util.Log.w("TourIt", "Upgrading database, which will destroy all old data");

db.execSQL("DROP TABLE IF EXISTS tours"); onCreate(db);

First, we query sqlite_master to see if our table is there - if it is, we're done. Otherwise, we execute some SQL to create the table, then scan the SD card to see if we can find any tours that need to be loaded. Those are poured into the table via insert() calls.

The method behind this madness is covered in greater detail in Appendix A, where we cover TourIt in more detail.

query()

As one might expect, the query() method is where your content provider gets details on a query some activity wants to perform. It is up to you to actually process said query.

The query method gets, as parameters:

• A Uri representing the collection or instance being queried

• A string[] representing the list of properties that should be returned

• A String representing what amounts to a SQL WHERE clause, constraining which instances should be considered for the query results

• A string[ ] representing values to "pour into" the where clause, replacing any ? found there

• A string representing what amounts to a SQL order by clause

You are responsible for interpreting these parameters however they make sense and returning a Cursor that can be used to iterate over and access the data.

As you can imagine, these parameters are aimed towards people using a SQLite database for storage. You are welcome to ignore some of these parameters (e.g., you elect not to try to roll your own SQL where clause parser), but you need to document that fact so activities only attempt to query you by instance Uri and not using parameters you elect not to handle.

For SQLite-backed storage providers, however, the query() method implementation should be largely boilerplate. Use a SQLiteQueryBuilder to convert the various parameters into a single SQL statement, then use

query() on the builder to actually invoke the query and give you a Cursor back. The Cursor is what your query() method then returns.

For example, here is query() from Provider:

(Override public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs, String sort) { SQLiteQueryBuilder qb=new SQLiteQueryBuilder();

qb.setTables(getTableName());

if (isCollectionUri(url)) {

qb.setProjectionMap(getDefaultProjection());

qb.appendWhere(getIdColumnName()+"=" + url.getPathSegments().get(1));

String orderBy;

if (TextUtils.isEmpty(sort)) { orderBy=getDefaultSortOrder();

orderBy=sort;

Cursor c=qb. query(db, projection, selection, selectionArgs, null, null, orderBy);

c.setNotificationUri(getContext().getContentResolver(), url); return c;

We create a SQLiteQueryBuilder and pour the query details into the builder. Note that the query could be based around either a collection or an instance Uri - in the latter case, we need to add the instance ID to the query. When done, we use the query() method on the builder to get a Cursor for the results.

insert()

Your insert() method will receive a Uri representing the collection and a Contentvalues structure with the initial data for the new instance. You are responsible for creating the new instance, filling in the supplied data, and returning a Uri to the new instance.

If this is a SQLite-backed content provider, once again, the implementation is mostly boilerplate: validate that all required values were supplied by the activity, merge your own notion of default values with the supplied data, and call insert() on the database to actually create the instance.

For example, here is insert() from Provider:

(Override public Uri insert(Uri url, ContentValues initialValues) { long rowlD; ContentValues values;

if (initialValues!=null) {

values=new ContentValues(initialValues); } else {

values=new ContentValues();

throw new IllegalArgumentException("Unknown URL " + url);

for (String colName : getRequiredColumns()) { if (values.containsKey(colName) == false) {

throw new IllegalArgumentException("Missing column: "+colName);

populateDefaultValues(values);

rowID=db.insert(getTableName(), getNullColumnHack(), values); if (rowlD > 0) {

Uri uri=ContentUris.withAppendedId(getContentUri(), rowlD); getContext().getContentResolver().notifyChange(uri, null); return uri;

throw new SQLException("Failed to insert row into " + url);

The pattern is the same as before: use the provider particulars plus the data to be inserted to actually do the insertion. Of note:

• You can only insert into a collection Uri, so we validate that by calling isCollectionUri()

• The provider also knows what columns are required (getRequiredColumns()), so we iterate over those and confirm our supplied values cover the requirements

• The provider is also responsible for filling in any default values (populateDefaultValues()) for columns not supplied in the insert() call and not automatically handled by the SQLite table definition update()

Your update() method gets the Uri of the instance or collection to change, a ContentValues structure with the new values to apply, a String for a SQL where clause, and a string[ ] with parameters to use to replace ? found in the where clause. Your responsibility is to identify the instance(s) to be modified (based on the Uri and where clause), then replace those instances' current property values with the ones supplied.

This will be annoying, unless you're using SQLite for storage. Then, you can pretty much pass all the parameters you received to the update() call to the database, though the update() call will vary slightly depending on whether you are updating one instance or several.

For example, here is update() from Provider:

(Override public int update(Uri url, ContentValues values, String where, String[] whereArgs) { int count;

if (isCollectionUri(url)) {

count=db.update(getTableName(), values, where, whereArgs);

String segment=url.getPathSegments().get(1); count=db

.update(getTableName(), values, getIdColumnName()+"="

+ segment

+ (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : ""), whereArgs);

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

return count;

In this case, updates can either be to a specific instance or applied across the entire collection, so we check the Uri (isCollectionUri()) and, if it is an update for the collection, just perform the update. If we are updating a single instance, we need to add a constraint to the where clause to only update for the requested row.

delete()

As with update(), delete() receives a Uri representing the instance or collection to work with and a where clause and parameters. If the activity is deleting a single instance, the Uri should represent that instance and the where clause may be null. But, the activity might be requesting to delete an open-ended set of instances, using the where clause to constrain which ones to delete.

As with update(), though, this is simple if you are using SQLite for database storage (sense a theme?). You can let it handle the idiosyncrasies of parsing and applying the where clause - all you have to do is call delete() on the database.

For example, here is delete() from Provider:

^Override public int delete(Uri url, String where, String[] whereArgs) { int count; long rowId=0;

if (isCollectionUri(url)) {

count=db.delete(getTableName(), where, whereArgs);

String segment=url.getPathSegments().get(1);

rowId=Long.parseLong(segment);

count=db

.delete(getTableName(), getIdColumnName()+"=" + segment

+ (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : ""), whereArgs);

getContext().getContentResolver().notifyChange(url, null); return count;

This is almost a clone of the update() implementation described above -either delete a subset of the entire collection or delete a single instance (if it also satisfies the supplied where clause).

getType()

The last method you need to implement is getType(). This takes a Uri and returns the MIME type associated with that Uri. The Uri could be a collection or an instance Uri; you need to determine which was provided and return the corresponding MIME type.

For example, here is getType() from Provider:

(Override public String getType(Uri url) { if (isCollectionUri(url)) { return(getCollectionType());

return(getSingleType());

As you can see, most of the logic delegates to private getCollectionType() and getSingleType() methods:

private String getCollectionType() {

return("vnd.android.cursor.dir/vnd.commonsware.tour");

private String getSingleType() {

return("vnd.android.cursor.item/vnd.commonsware.tour");

DatabaseContentProvider

If you want to use DatabaseContentProvider as a base class, here is what you need to do:

• You still need getType() as described in the preceding section

• You may elect to override onCreate() for your own initialization, but be sure to chain upward to the superclass (super.onCreate())

• You may elect to override upgradeDatabases() to rebuild your tables if your database schema has changed

• You need to implement queryInternal(), insertInternal(), updateInternal(), and deleteInternal() much as described above for query(), insert(), update(), and delete() respectively

Self Publishing

Self Publishing

Have you always wanted to write your own book and get it published? Discover How to Write, Print Sell Your Own Book! Have you always wanted to write your own book and get it published? If you have written a book or even if you have a yen to be a writer, you are probably already aware of the competition in the writing field.

Get My Free Ebook


Post a comment