Android Sharing Application Data with Content Provider and Content Resolver

In the previous article (must read) we discussed how basic CRUD operations can be done on SQLite in Android. An SQLite database is inherently private to the application that creates it. That means if you want some other app to access your data that won’t be possible. To solve this limitation (its more of a security measure actually), Android has the concept of Content Providers using which one app can give out constrained access to its structured set of data to other apps. So a content provider allows application to access its data which is mostly stored in an SQLite database. It is important to note that just because providers allow you to share data with other applications doesn’t mean that it cannot be used in the same application to access data.

The Android platform itself includes content providers that facilitates access to personal contact information, audio, video, images, calendar events and reminders, etc. It’s not necessary to develop our own providers unless we want to share data with other apps.

What's the one thing every developer wants? More screens! Enhance your coding experience with an external monitor to increase screen real estate.

A ContentResolver object in our app’s Context is used to access data in a content provider. The ContentResolver client object sends requests to the ContentProvider implementation’s object. This makes the provider perform the requested actions and return results.

Accessing a Content Provider

The documentation on Content Provider Basics contains most of what’ll be found in this section. I’ll just try to summarize the important parts. All the code examples will be based off what you’ll already find there.

As discussed earlier, a ContentResolver client object is used to access the data from a content provider. This object has methods like query(), insert(), update(), delete(), etc. providing basic CRUD (create, read, update, delete) functions that call same-named methods in the provider object. A provider object is an instance of the subclass of ContentProvider. So if you notice ContentProvider class has all the aforementioned methods – query(), insert(), update(), delete().

Android provides us with a user dictionary built-in provider that stores the spellings of non-standard words (not part of Android’s dictionary of words) that the user wants the platform to remember and predict (suggest) while the user is typing so that long words (for instance an email) can be typed quickly without typing the full word. We’ll see how the basic CRUD operations can be performed on this provider.

Retrieve Data

Before you can read from or write to the user dictionary, it is important to request appropriate access permission. To retrieve data, you’ll require the android.permission.READ_USER_DICTIONARY permission whereas android.permission.WRITE_USER_DICTIONARY permission is necessary for inserting, updating and deleting data to this content provider.

<uses-permission android:name="android.permission.READ_USER_DICTIONARY">
<uses-permission android:name="android.permission.WRITE_USER_DICTIONARY">

Once the permission has been requested (and granted) it’s time to retrieve data by calling ContentResolver.query() that calls the ContentProvider.query() method defined by the User Dictionary Provider. Let’s take a look at the query() syntax:

Cursor cursor = getContentResolver().query(
        UserDictionary.Words.CONTENT_URI,   // Maps to the provider's table name
        projection,                         // Array of columns to return for each row (col1, col2, col3)
        selectionClause,                    // Columns for WHERE clause
        selectionArgs,                      // Values for each corresponding column in WHERE clause
        sortOrder                           // Columns to ORDER BY
);

Based on the comments you can easily relate each argument to a portion of the eventually formed SQL query. The content URI needs some explanation that we’ll discuss in a bit.

Now let’s see how to construct a query and then execute ContentResolver.query() on the user dictionary provider.

// "projection" defines the columns that will be returned for each row
String[] projection = {
        UserDictionary.Words._ID,       // Contract class constant for the _ID column
        UserDictionary.Words.WORD,      // Contract class constant for the word column
        UserDictionary.Words.LOCALE     // Contract class constant for the locale column
};

// Defines WHERE clause columns and placeholders
String selectionClause = UserDictionary.Words._ID + " = ?";

// Define the WHERE clause's placeholder values
String[] selectionArgs = { "1" };

Cursor cursor = getContentResolver().query(
        UserDictionary.Words.CONTENT_URI,
        projection,
        selectionClause,
        selectionArgs,
        UserDictionary.Words.DEFAULT_SORT_ORDER // "frequency DESC"
);

int idIndex = cursor.getColumnIndex(UserDictionary.Words._ID);
int wordIndex = cursor.getColumnIndex(UserDictionary.Words.WORD);
int localeIndex = cursor.getColumnIndex(UserDictionary.Words.LOCALE);

// Log.d(TAG, "Count: " + cursor.getCount());

if (cursor == null) {
    // Some providers return null if an error occurs whereas others throw an exception
}
else if (cursor.getCount() < 1) {
    // No matches found
}
else {

    while (cursor.moveToNext()) {
        int id = cursor.getInt(idIndex);
        String word = cursor.getString(wordIndex);
        String locale = cursor.getString(localeIndex);

        // Dumps "ID: 1 Word: NewWord Locale: en_US"
        // I added this via Settings > Language & Input > Personal Dictionary
        Log.d(TAG, "ID: " + id + " Word: " + word + " Locale: " + locale);
    }

}

As you may already know, the placeholder actually protects your apps against SQL injection. The query that we constructed is equivalent to this SQL statement:

SELECT _id, word, locale FROM words WHERE _id = 1 ORDER BY frequency DESC

You can find a list of the UserDictionary.Words contract class constants here.

We’ve already discussed the concept of Cursors in my previous post on working with SQLite databases in Android. Also I’ve discussed how Cursors can be directly used in conjunction with ListViews in my understanding Android lists article.

Content URIs

Content URIs identifies data in a content provider. For example the constant that we used before – UserDictionary.Words.CONTENT_URI – actually holds this content URI string/value – content://user_dictionary/words. This URI consists of three parts:

  • content:// – This is the scheme which is always present in a content URI. It identifies a string as a content URI.
  • user_dictionary – It is known as the authority string of the provider.
  • words – This is usually the table name with which queries will interact to which this content URI is passed.

The ContentResolver object parses the URI to pull out the authority that it matches against a system table of known providers to resolve the correct provider to dispatch the query arguments to.

Now many providers allows us to access a single record in a table just by appending the ID value of that row to the end of the content URI. In such a case, a row whose _ID is 2 will have a content URI like this – content://user_dictionary/words/2. You can use ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI, 2) to append an ID to the end of the path.

Insert Data

Just like we fetched data from the Content Provider, we can also modify data. Let’s see how we can create new rows into the provider by calling the ContentResolver.insert() method.

// Insertion

ContentValues values = new ContentValues();

values.put(UserDictionary.Words.APP_ID, "com.example.app");
values.put(UserDictionary.Words.LOCALE, "en_US");
values.put(UserDictionary.Words.WORD, "WeeeeWord");
values.put(UserDictionary.Words.FREQUENCY, "100");

Uri uri = getContentResolver().insert(UserDictionary.Words.CONTENT_URI, values);

// Dumped "content://user_dictionary/words/2"
Log.d(TAG, uri.toString());

We just created a new record in the words table. The URI returned is content://user_dictionary/words/2 where 2 is the _ID value that is maintained automatically and can be retrieved by calling ContentUris.parseId(uri).

Update Data

Updating rows is similar to insertion where we have to use a ContentValue object that contains new values for the columns. We can make use of selection clause and selection args (WHERE conditions) to limit the number of rows to update based on the criteria.

// Updating

ContentValues values = new ContentValues();
values.put(UserDictionary.Words.WORD, "Weeee");

String selectionClause = UserDictionary.Words.WORD + " LIKE ?";
String[] selectionArgs = { "WeeeeWord" };

int rowsUpdated = getContentResolver().update(
        UserDictionary.Words.CONTENT_URI,
        values,
        selectionClause,
        selectionArgs
);

// Dumps 1
Log.d(TAG, String.valueOf(rowsUpdated));

Delete Data

Deleting records is similar to retrieving by specifying the selection criteria and calling ContentResolver.delete().

// Deletion

String selectionClause = UserDictionary.Words.WORD + " LIKE ?";
String[] selectionArgs = { "Weeee" };

int rowsDeleted = getContentResolver().delete(
        UserDictionary.Words.CONTENT_URI,
        selectionClause,
        selectionArgs
);

// Dumps 1
Log.d(TAG, String.valueOf(rowsDeleted));

The documentation mentions certain alternative forms of accessing a Provider’s data like using Intents or inserting a large number of rows in a single method call here. These forms are slightly tricky but worth knowing.

MIME Types

Providers maintain a MIME data type associated with each content URI that they define that can be fetched by calling ContentResolver.getType() on the URI. The documentation has a MIME type reference that explains the syntax for both standard and custom MIME types.

More Built-in Providers

Although we’ve just taken a look at one provider in Android which is the User Dictionary Provider, there are many other providers that the framework contains. You can find some of them in the android.provider package summary like ContactsContract, MediaStore, CalendarContract, Telephony, etc.

Wrapping Up

So now that we’ve a good understanding of Content Providers and Content Resolvers and how they can be used to provide data from one point (data source) to another, in the next article we’ll dive into how to create a custom content provider rather than just using the built-in providers provided by the Android framework.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download

Author: Rishabh

Rishabh is a full stack web and mobile developer from India. Follow me on Twitter.

Leave a Reply

Your email address will not be published. Required fields are marked *