Android Fundamentals: Working With Content Providers

Android Fundamentals: Working With Content Providers

Tutorial Details
  • Technology: Eclipse + Android SDK
  • Difficulty: Advanced Intermediate
  • Estimated Completion Time: 60-90 Minutes
This entry is part 1 of 8 in the series Android Fundamentals

The TutList application that we’ve been working with has a pretty big flaw right now: the article data is not “live”, but static content. In this tutorial, you take several more steps towards a flexible and expandable solution by modifying the application to act as a data-backed content provider.

The Android framework uses a concept called content providers to enable applications to share and use data across the platform. Typically, a provider is backed by a SQLite database where the underlying data is stored. The current state of the TutList application is such that it gets the data for the ListView from static string arrays in the resources. In this tutorial, we’ll remove those fixed data resources and build a flexible database-driven content provider in their place. You’ll see that the user interface code won’t change much. The back-end, however, will get more complex. The advantage here is that when we finally switch the application over to retrieving live data from the Internet, we’ll have a place to store and manage it easily.

The pacing of this tutorial will be faster than some of our beginner tutorials; you may have to review some of the other Android tutorials on this site or even in the Android SDK reference if you are unfamiliar with any of the basic Android concepts and classes discussed in this tutorial. You can read the SQLite Crash Course for Android Developers tutorial to refresh your SQLite knowledge. The final sample code that accompanies this tutorial is available for download as open-source from the Google code hosting.

Step 0: Getting Started

This tutorial assumes you will start where our last tutorial, Android Compatibility: Working with Fragments, left off. You can download that code and build from there or you can download the code for this tutorial and follow along. Either way, get ready by downloading one or the other project and importing it into Eclipse.

Step 1: Creating the Database Class

First, you must create the application’s underlying SQLite database. Begin by creating a new class named TutListDatabase that extends from SQLiteOpenHelper. We placed it in the com.mamlambo.tutorial.tutlist.data package to separate it out from the user interface portion of the app. While you’re at it, define the database configuration information in the class, such as the name of the database and its version number. We’ve done this with constants. Finally, use update the TutListDatabase class constructor to reference these values, as follows:

public class TutListDatabase extends SQLiteOpenHelper {
    private static final String DEBUG_TAG = "TutListDatabase";
    private static final int DB_VERSION = 1;
    private static final String DB_NAME = "tutorial_data";
    public TutListDatabase(Context context) {
        super(context, DB_NAME, null, DB_VERSION);
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
    }
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }

We’ll get to the implementation of the onCreate() and onUpgrade() methods shortly.

Step 2: Defining the Database Schema

Our initial article data had just two data fields: a title and a link. There’s no reason not to keep this sort of structure in our SQLite database. We’ll need a mandatory _id field, too, which acts as a unique identifier for each record. We’ll call this table tutorials.

This simple schema will suite us for now. To simplify usage and assist with other aspects of the system, we’ll define the columns, tables, and even the create statement as static strings in the TutListDatabase class, like so:

public static final String TABLE_TUTORIALS = "tutorials";
public static final String ID = "_id";
public static final String COL_TITLE = "title";
public static final String COL_URL = "url";
private static final String CREATE_TABLE_TUTORIALS = "create table " + TABLE_TUTORIALS
+ " (" + ID + " integer primary key autoincrement, " + COL_TITLE
+ " text not null, " + COL_URL + " text not null);";
private static final String DB_SCHEMA = CREATE_TABLE_TUTORIALS;

Step 3: Creating the Database

Database creation should now be fairlystraightforward. Within the onCreate() method, simply execute the DB_SCHEMA string as SQL to create the table you defined in the previous step:

@Override
public void onCreate(SQLiteDatabase db) {
    db.execSQL(DB_SCHEMA);
}

This might be a good opportunity to insert some sample data (articles) for use. You could use a typical “INSERT INTO” SQLite command. See the code download for an example of how to do this.

Step 4: Upgrading the Database

Since the database is brand new, we have no upgrade policy we need to follow. Therefore, we’ll just drop the table and recreate it if an upgrade request is made.

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    Log.w(DEBUG_TAG, "Upgrading database. Existing contents will be lost. ["
            + oldVersion + "]->[" + newVersion + "]");
    db.execSQL("DROP TABLE IF EXISTS " + TABLE_TUTORIALS);
    onCreate(db);
}

If the database version is incremented and the app installed over an existing installation, this code will be triggered. The warning, only shown to LogCat, simply states that existing contents will be lost. If you implemented sample content creation as part of the previous step, the initial content will be restored during the creation. In a published application that stores important user data, you would likely want to do everything possible to keep that data by migrating it to from the old schema to the new schema. However, for this simple example, there is little need for such provisions.

As the database schema hasn’t changed and remains compatible, there’s no reason to update the database version. The database contents will remain intact.

Step 5: Creating the Content Provider Class

Now that your application has a functional database, you can turn your attention to implementing a content provider to access, expose, and manage the article data stored there. Begin by createing a new class named TutlistProvider which extends the ContentProvider class. Give it a private member variable to hold an instance of a TutListDatabase. Instantiate the database in the onCreate() method:

public class TutListProvider extends ContentProvider {
    private TutListDatabase mDB;
    @Override
    public boolean onCreate() {
        mDB = new TutListDatabase(getContext());
        return true;
    }

Step 6: Preparing Helper Constants and the Matcher

Content providers work with data at the URI level. For instance, this URI identifies all of the tutorials:

content:// com.mamlambo.tutorial.tutlist.data.TutListProvider/tutorials

However, this identification doesn’t actually happen by magic. Instead, content provider classes generally provide some public constants that can be used by apps to identify the data they want to query. Inside the content provider, some help is available for determining what kind of URI is being passed in. This will become clearer with the coming examples and code.

For now, here is a set of constants found in the TutListProvider class to use in the various methods and for use by external classes using this content provider :

private static final String AUTHORITY = "com.mamlambo.tutorial.tutlist.data.TutListProvider";
public static final int TUTORIALS = 100;
public static final int TUTORIAL_ID = 110;
private static final String TUTORIALS_BASE_PATH = "tutorials";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY
        + "/" + TUTORIALS_BASE_PATH);
public static final String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE
        + "/mt-tutorial";
public static final String CONTENT_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE
        + "/mt-tutorial";

Note which definitions are private and which are public—this is purposeful. The public definitions will be used by the other portions of the app (or other apps who want to access the content provider data). The private definitions are for internal use by the class only and not exposed to others. To determine what kinds of URI addresses are passed to the content provider, we can leverage a helper class called UriMatcher to define specific URI patterns the content provider will support. Here’s the UriMatcher for our content provider, defined statically at the class level inside TutListProvider:

private static final UriMatcher sURIMatcher = new UriMatcher(
        UriMatcher.NO_MATCH);
static {
    sURIMatcher.addURI(AUTHORITY, TUTORIALS_BASE_PATH, TUTORIALS);
    sURIMatcher.addURI(AUTHORITY, TUTORIALS_BASE_PATH + "/#", TUTORIAL_ID);
}

This UriMatcher defines two types of URIs. One looks like the sample one from above. The other is simply appended with a forward slash (/) following by a number. That type of URI is used to supply the unique identifier for a specific article, so as to return a single entry.

Step 6: Handling Content Provider Queries

A content provider has several methods we must override. The only one we’re currently interested in is the query() method. The others are delete(), getType(), insert(), and update() — we’ll get to those near the end of this tutorial.
The query() method initially looks complex, with five parameters, including two arrays. As it turns out, the Android SDK has another helper class that greatly simplifies the implementation of this method. The SQLiteQueryBuilder class is just what we’re looking for. Here’s the complete, and simple, implementation of the query() method:

@Override
public Cursor query(Uri uri, String[] projection, String selection,
        String[] selectionArgs, String sortOrder) {
    SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
    queryBuilder.setTables(TutListDatabase.TABLE_TUTORIALS);
    int uriType = sURIMatcher.match(uri);
    switch (uriType) {
    case TUTORIAL_ID:
        queryBuilder.appendWhere(TutListDatabase.ID + "="
                + uri.getLastPathSegment());
        break;
    case TUTORIALS:
        // no filter
        break;
    default:
        throw new IllegalArgumentException("Unknown URI");
    }
    Cursor cursor = queryBuilder.query(mDB.getReadableDatabase(),
            projection, selection, selectionArgs, null, null, sortOrder);
    cursor.setNotificationUri(getContext().getContentResolver(), uri);
    return cursor;
}

To start, we get a new instance of the SQLiteQueryBuilder class. Then we use the setTables() method to specify the tables we’re working with—in this case, just the tutorials table. Next we use the UriMatcher class to do the heavy lifting of determining if the query is for a single entry or all entries. If it’s a single entry, we add a where clause to filter by just the unique id.
Next, we call the query() method of the SQLiteQueryBuilder class. Turns out that it takes many of the same parameters as were passed to our query() method — so we just pass them along. Then we return the newly created cursor.
The setNotificationUri() method simply places a watch on the caller’s content resolver such that if the data changes and the caller has a registered change watcher, they’ll be notified. Here we just use the same URI.

Step 7: Registering the Content Provider

Like an Activity class, a Content Provider must be properly registered in the Android Manifest file. This means adding a section within the section of the file, like follows:

<provider
    android:authorities="com.mamlambo.tutorial.tutlist.data.TutListProvider"
    android:multiprocess="true"
    android:name="com.mamlambo.tutorial.tutlist.data.TutListProvider"></provider>

The authorities attribute should match the AUTHORITY constant defined in the TutListProvider class as that’s the authority used with the URIs. The name attribute must be the fully qualified class name of the content provider.

Step 8: Updating the ListView

With the content provider implementation complete, let’s update the application to use it! Within TutListFragment class, update the onCreate() method to use the new content provider as follows:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    String[] projection = { TutListDatabase.ID, TutListDatabase.COL_TITLE };
    String[] uiBindFrom = { TutListDatabase.COL_TITLE };
    int[] uiBindTo = { R.id.title };
    Cursor tutorials = getActivity().managedQuery(
            TutListProvider.CONTENT_URI, projection, null, null, null);
    CursorAdapter adapter = new SimpleCursorAdapter(getActivity()
            .getApplicationContext(), R.layout.list_item, tutorials,
            uiBindFrom, uiBindTo);
    setListAdapter(adapter);
}

A projection is simply a list of columns to use with the adapter. The ListView uses the titles and can provide an id when an item is clicked. For use with an adapter, the id column must be named “_id” — which we’ve done.

Next, you’ll need to update the onListItemClick() method of the ListView. Before, we simply used the position to look up the link in an array. Now we’ll use the unique id – which matches the database id, conveniently enough — and look up the link in the database via a simple query to the content provider:

@Override
public void onListItemClick(ListView l, View v, int position, long id) {
    String projection[] = { TutListDatabase.COL_URL };
    Cursor tutorialCursor = getActivity().getContentResolver().query(
            Uri.withAppendedPath(TutListProvider.CONTENT_URI,
                    String.valueOf(id)), projection, null, null, null);
    if (tutorialCursor.moveToFirst()) {
        String tutorialUrl = tutorialCursor.getString(0);
        tutSelectedListener.onTutSelected(tutorialUrl);
    }
    tutorialCursor.close();
}

Here, we request the URL column and use the content URI with the id appended to it. Pretty straightforward, right?

And guess what? You’re done! You can run the application now and it should look — and behave — exactly as it did before. However, instead of the data being sourced from the fixed resources, the app is now storing its data in a new, improved home—a database, and accessed through a straightforward mechanism—a content provider.

All of that, and we’re right back where we started. Feels a bit anticlimactic, huh? Actually, you’re not quite finished. You should finish off the rest of the content provider methods.

Step 9: Finishing the Content Provider

Although the application does not yet use the insert(), update(), getType(), or delete() methods, it will in the future when you start grabbing “live” tutorial data from a remote source. The implementation of each of these methods follows a pattern similar to that of the query() method. For instance, here’s the implementation of the delete() method:

@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
    int uriType = sURIMatcher.match(uri);
    SQLiteDatabase sqlDB = mDB.getWritableDatabase();
    int rowsAffected = 0;
    switch (uriType) {
    case TUTORIALS:
        rowsAffected = sqlDB.delete(TutListDatabase.TABLE_TUTORIALS,
                selection, selectionArgs);
        break;
    case TUTORIAL_ID:
        String id = uri.getLastPathSegment();
        if (TextUtils.isEmpty(selection)) {
            rowsAffected = sqlDB.delete(TutListDatabase.TABLE_TUTORIALS,
                    TutListDatabase.ID + "=" + id, null);
        } else {
            rowsAffected = sqlDB.delete(TutListDatabase.TABLE_TUTORIALS,
                    selection + " and " + TutListDatabase.ID + "=" + id,
                    selectionArgs);
        }
        break;
    default:
        throw new IllegalArgumentException("Unknown or Invalid URI " + uri);
    }
    getContext().getContentResolver().notifyChange(uri, null);
    return rowsAffected;
}

The first couple of lines determine the type of the incoming URI and open a writable database. Then, if the URI points to the list, possibly with a filter, we just delete that. Note that the URI with no filter (selection and selectionArgs) will delete all entries. Otherwise, we delete based on a specific ID — with or without a filter.
The rest of the methods are found in the downloadable (and online viewable) open source code. They are similar and you should be able to read through them to see how they work. The gist is that they each do the “right” thing depending on the type of URI. Since this content provider is database-backed, the right thing usually involves calling an equivalent SQLite method, greatly simplifying the interface implementation.

Conclusion

This tutorial has taught you not only how to create a SQLite database and wrap it inside of a content provider, but also how straightforward it is to use a content provider to populate a ListView control. In future tutorials, we’ll extend this application further to populate the application’s database with fresh, live tutorial content and more.
We hope you’ve enjoyed this tutorial. We look forward to your feedback on the pacing and complexity of the material covered.

About the Authors

Mobile developers Lauren Darcey and Shane Conder have coauthored several books on Android development: an in-depth programming book entitled Android Wireless Application Development and Sams Teach Yourself Android Application Development in 24 Hours. When not writing, they spend their time developing mobile software at their company and providing consulting services. They can be reached at via email to androidwirelessdev+mt@gmail.com, via their blog at androidbook.blogspot.com, and on Twitter @androidwireless.

Need More Help Writing Android Apps? Check out our Latest Books and Resources!

Buy Android Wireless Application Development, 2nd Edition  Buy Sam's Teach Yourself Android Application Development in 24 Hours  Mamlambo code at Code Canyon

Series NavigationAndroid Fundamentals: Properly Loading Data»

Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • http://blog.matto1990.com Matt Oakes

    Brilliant! Many more articles like this would be amazing. This is the sort of thing Android devs needs. This information is far to hard to find direct from Google.

  • http://matthewwatts.info/contact 7227

    Great Tut. Let’s see more of these (for Android).

  • Bruno Antônio

    Another great tutorial, guys.

    Anyone knows how i can use this code with more than one table? I am trying to add a category table, for example. Thanks!

  • Deb

    Hi,
    Nice tutorial, cleared up some misconception of mine. :-)

    I also have a question for you regarding content providers. As far as I know, when we query a content provider, it doesn’t support GROUP BY for whatever reason. Now, when I work with SQL and I want to get the most occuring value in a column I can use MAX(COUNT(filed)) along with a GROUP BY statement. How do I do this, in case I am working with a content provider? I guess I can implement this feature if I am coding my own content providers, but what about if I am using an Android built in content provider like Media, CallLog etc.? One option is to create a temp table from the content provider data and query the temp table, not very feasible in all cases, right? What’s the best way to do this?

  • Fred

    This was extremely useful as an example of creating a ContentProvider based on a SQLite database. Thank you.

  • Berni

    Can a SQLite database on Android be accessed from outside? Eg. from a server? I want to acquire data with an Android tab and submit them to a (Web) Server.

  • David

    Let others know that this is the best way of retrieving data in Android that I’ve been able to find so far.

    I lost so much time using and try to implement other methods that just gave me problems down the road.

  • Hank

    Outside of the argument of providing data to other applications. What’s the advantage of using a Content Provider instead of just direct database access for this application?

  • http://www.vogella.de Lars Vogel

    Thank you, a very nice example for creating a content provider. Should you not close the database at some point in the ContentProvider?

  • Kaushik

    Great Tutorial for beginners. Thanks a lot

  • Alex Lockwood

    Note that the “multiprocess=”true”" attribute is not necessary here. See Diane Hackborn’s post on the topic: http://groups.google.com/group/android-developers/browse_thread/thread/bbd50c26d00b497c?pli=1

  • http://707monty.blogspot.com Pakistan politics

    u saved me in my lab exam, :)))))))

  • http://lemonkoala.com Lem Lordje Ko

    Don’t you have an indepent tutorial?

    I’m having a tutorial stack overflow.

  • http://vimaltuts.com Clara

    Great Tutorial for beginners like me to get start.

  • Ali Ustek

    I have checked out the code from SVN but still the ListView is empty, it doesn’t show anything on the emulator.

    Any ideas?

  • trebew

    Nice topic! I have a question about the onUpdate method. I want to change my database (add a columm in a table). The thing is that I can’t lose the data in the database. How can I use the method onUpdate to do such a thing? If you know another link, it will be of great help.

  • venky

    Nice tutorial………

  • Zack

    “adding a section within the section of the file” – this part really helped me a lot, I don’t think I could have done it otherwise… ;)

  • Zack

    Is there a reason why the content provider is exported? eclipse complains about it, so I added

    ‘android:exported=”false”‘ to the provider tag. Hope it isn’t going to break my implementation.

  • 9androidnet
  • nmeq

    The source code for download doesn’t work – you get class not found errors when trying to compile and downloading “mlist-code-3 tutorial”, and you failed to add id=R.id.title in list_item.xml. Trying to reference it in the onCreate method as above wont work but you didn’t show this in the tutorial.

    I lost hours on these mistakes! Probably doesn’t help that I am inexperienced but still. Please make sure your tutorials don’t have silly mistakes or omissions like this – totally ruins it otherwise. It is a good tutorial this aside….

  • nmeq

    sorry to go on but the list view/tutorial views also don’t populate as the database is empty. you missed the step where you explain how to actually populate the database so the listview actually fills up when you start the app – or have I missed something?