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.