Using Content Providers - A Skeleton Application for Android - Programming Android (2011)

Programming Android (2011)

Part III. A Skeleton Application for Android

Chapter 12. Using Content Providers

When Android applications share data, they rely on the content provider API to expose data within their database. For example, the Android contact content provider allows an unlimited number of applications to reuse contact persistence on the Android platform. By simply invoking this content provider, an application can integrate access to a user’s contacts stored locally and synchronized with the Google cloud. Applications can read and write data in content providers without having to provide their own database manipulation code. In this way, content providers provide a powerful feature that allows developers to easily create applications with sophisticated data management—in many cases, applications will end up writing very little data persistence code of their own.

The content provider API enables client applications to query the OS for relevant data using a Uniform Resource Identifier (URI), similar to the way a browser requests information from the Internet. For a given URI query, a client does not know which application will provide the data; it simply presents the OS with a URI and leaves it to the platform to start the appropriate application to provide the result. The platform also provides a permission that allows clients to limit access to content provider data.

The content provider API enables full create, read, update, and delete access to shared content. This means applications can use URI-oriented requests to:

§ Create new records

§ Retrieve one, all, or a limited set of records

§ Update records

§ Delete records

This chapter shows you how to write your own content provider by examining the inner workings of an example content provider, SimpleFinchVideoContentProvider, included within the Finch source tree. All file references are contained in the source directory for this chapter. Thus, when theAndroidManifest.xml file is referenced in this section, the $(FinchVideo)/AndroidManifest.xml file is assumed. We’ll use this code to describe how to create a content provider by implementing each method required by the main content provider API, the class ContentProvider. We will also explain how to integrate a SQLite database into that content provider. We’ll describe how to implement the basic function of a content provider, which is to provide a mapping between URIs that reference data and database rows. You will see how a content provider encapsulates data persistence functions and enables your application to share data across processes when you declare your provider in AndroidManifest.xml. We will show you how to hook content provider data into Android UI components, thus completing the MVC architecture that we have led up to so far in this book. Finally, we will build a data viewing activity that automatically refreshes its display in response to changes in data.

NOTE

Throughout this chapter, we make the assumption that local content provider storage uses a SQLite database. Given the content provider query, insert, update, and delete API methods, it’s actually a bit of a stretch to think about mapping it to anything else, even though, in theory, the API can store and retrieve data using any backend, such as a flat file, that could support the required operations.

We follow this introduction in the next chapter by showing you how to extend and enhance the very concept of a content provider. In the process, you will learn to leverage the content provider API to enable integration of RESTful network services into Android. This simple architecture will prevent many common mobile programming errors, even though it only relies on basic Android components. You will see that this approach leads logically into a mobile application architecture that adds significant robustness and performance improvements to Android applications.

We will walk through a video listing application that provides a simplified illustration of this architecture. This application will follow the suggested approach by loading, parsing, and caching YouTube video content entries from the RESTful web service at http://gdata.youtube.com. We’ll simply be using gData as an example of a RESTful service that we can integrate into an Android content provider. The application UI will use content providers to dynamically display video entries as they are loaded and parsed from the network. You will be able to apply this approach to integrate the large number of web services available on the Internet into your Android-based application. Incidentally, the gData URI provides a pretty neat demo from Google and worth checking out in its own right.

Understanding Content Providers

Content providers encapsulate data management so that other parts of an application, such as the view and controller, do not need to participate in persisting application data. Saying this in a different way: content providers persist application data because the view and controller should not handle it. Specialized software layers that do not attempt to perform tasks of other layers are the hallmark of well-crafted code. Bugs and unneeded complexity arise when software layers perform tasks that are beyond their scope. Thus, a UI should consist only of well-laid-out UI components fine-tuned to collect events from their end user. A well-written application controller will contain only the domain logic of the mobile application. And in connection with this chapter, simplifications arise when both types of code can outsource data persistence to a logical third party: content providers. Recalling the discussion from SQL and the Database-Centric Data Model for Android Applications, content providers are well suited to implementing the nondocument-centric data model.

With the assistance of a content provider, applications do not need to open their own SQLite tables, since that detail will take place behind the content provider interface in tables owned by the content provider. In the past, to share data, mobile applications might have had to store it in files in the local filesystem with an application-defined configuration format. Instead, with Android, applications can often rely solely on content provider storage.

Before digging into the SimpleFinchVideoContentProvider, we’ll provide an overview of the simple finch video application and provide background on content provider implementation tasks.

Implementing a Content Provider

To take advantage of this design structure, you will need to write your own content provider, which involves completing the following tasks:

§ Create a content provider public API for client consumption by:

§ Defining the CONTENT_URI for your content provider

§ Creating column names for communication with clients

§ Declaring public static String objects that clients use to specify columns

§ Defining MIME types for any new data types

§ Implement your content provider. This requires the following:

§ Extending the main content provider API, the ContentProvider class, to create a custom content provider implementation

§ Setting up a provider URI

§ Creating a SQLite database and associated cursors to store content provider data

§ Using cursors to make data available to clients while supporting dynamic data updates

§ Defining the process by which binary data is returned to the client

§ Implementing the basic query, insert, update, and delete data methods of a Cursor to return to the client

§ Update the AndroidManifest.xml file to declare your <provider>.

When we have finished discussing the implementation of a basic content provider, we will describe tasks related to using content providers to develop the more advanced network architecture that we have mentioned.

Browsing Video with Finch

The Finch video viewer enables users to list video-related metadata. We have included two versions of a video listing application, and two versions of underlying content providers. The first version, presented in this chapter, is a simple video listing application that usesSimpleFinchVideoContentProvider, which is designed to teach you to implement your first content provider. A second version of the app, presented in the next chapter, uses a slightly more complex content provider that adds the ability to pull content from the online YouTube video search service. This second version of the app has the ability to cache results and the ability to show video thumbnails.

Now we will explore the first app in detail. This simple application has one activity: SimpleFinchVideoActivity, which allows a user to create and list his own video metadata (e.g., video title, description, URI, and ID), as shown in Figure 12-1.

An activity for our simple video provider that lets users enter their own video “metadata”

Figure 12-1. An activity for our simple video provider that lets users enter their own video “metadata”

To use this application, simply enter appropriate data for a “video” entry, and then press the Insert button. The list underneath the text fields uses Android MVC to automatically refresh its view of data.

The simple video database

To store the data you enter into this application, the SimpleFinchVideoContentProvider class creates its database with the following SQL statement:

CREATE TABLE video (_id INTEGER PRIMARY KEY, title TEXT, decription TEXT, uri TEXT);

The _id column is required for use with the Android cursor system. It provides the unique identity of a row in a cursor as well as the identity of an object in the database. As such, you need to define this column with the SQL attributes INTEGER PRIMARY KEY AUTOINCREMENT to make certain its value is unique.

The title and description columns store video title and description data, respectively. The uri column contains a media URI that could be used to play a video entry in an actual working version of this application.

Structure of the simple version of the code

This section briefly examines relevant files within the simple Finch video application:

AndroidManifest.xml

We’ve created a manifest for a simple video content provider application that will contain a reference to our activity SimpleFinchVideoActivity as well as our content provider SimpleFinchVideoContentProvider.

$(FinchVideo)/src/com/oreilly/demo/pa/finchvideo/FinchVideo.java

The FinchVideo class contains the AUTHORITY attribute (discussed later) and the SimpleVideo class that defines the names of the content provider columns. Neither the FinchVideo class nor the SimpleVideo class contains any executable code.

$(FinchVideo)/src/com/oreilly/demo/pa/finchvideo/provider/SimpleFinchVideoContentProvider.java

The SimpleFinchVideoContentProvider class is the content provider for the simple video database. It handles URI requests as appropriate for the simple video application. This file is the subject of the first half of this chapter.

$(FinchVideo)/src/com/oreilly/demo/pa/finchvideo/SimpleFinchVideoActivity.java

The SimpleFinchVideoActivity class is an activity that allows the user to view a list of videos.

Defining a Provider Public API

Though we saw in Chapter 3 how clients use content providers, we provide more information here for content provider authors to fully implement the provider public API. For clients to use your content provider, you will need to create a public API class that contains a set of constants that clients use to access column fields of Cursor objects returned by your provider’s query method. This class will also define the content provider authority URI that provides the foundation of the whole provider URI communication scheme. Our class, FinchVideo.SimpleVideos, provides the API to our SimpleFinchVideo.

First we’ll explain the class in pieces, providing background on its fields, and then we’ll show a full listing.

Defining the CONTENT_URI

For a client application to query content provider data, it needs to pass a URI that identifies relevant data to one of the Android content resolver’s data access methods. These methods, query, insert, update, and delete, mirror the methods found on a content resolver that we define in Writing and Integrating a Content Provider. On receiving such an invocation, the content resolver will use an authority string to match the incoming URI with the CONTENT_URI of each content provider it knows about to find the right provider for the client. Thus, the CONTENT_URI defines the type of URIs your content provider can process.

A CONTENT_URI consists of these parts:

content://

A prefix that tells the Android Framework that it must find a content provider to resolve the URI.

The authority

This string uniquely identifies the content provider and consists of up to two sections: the organizational section and the provider identifier section. The organizational section uniquely identifies the organization that created the content provider. The provider identifier section identifies a particular content provider that the organization created. For content providers that are built into Android, the organizational section is omitted. For instance, the built-in “media” authority that returns one or more images does not have the organizational section of the authority. However, any content providers that are created by developers outside of Google’s Android team must define both sections of the content provider. Thus, the simple Finch video example application’s authority is com.oreilly.demo.pa.finchvideo.SimpleFinchVideo. The organizational section iscom.oreilly.demo.pa.finchvideo, and the provider identifier section is SimpleFinchVideo. The Google documentation suggests that the best solution for picking the authority section of your CONTENT_URI is to use the fully qualified class name of the class implementing the content provider.

The authority section uniquely identifies the particular content provider that Android will call to respond to queries that it handles.

The path

The content provider can interpret the rest of the URI however it wants, but it must adhere to some requirements:

§ If the content provider can return multiple data types, the URI must be constructed so that some part of the path specifies the type of data to return.

For instance, the built-in “contacts” content provider provides many different types of data: people, phones, contact methods, and so on. The contacts content provider uses strings in the URI to differentiate which type of data the user is requesting. Thus, to request a specific person, the URI will be something like this:

content://contacts/people/1

To request a specific phone number, the URI could be something like this:

content://contacts/people/1/phone/3

In the first case, the MIME data type returned will be vnd.android.cursor.item/person, whereas in the second case, it will be vnd.android.cursor.item/phone.

§ The content provider must be capable of returning either one item or a set of item identifiers. The content provider will return a single item when an item identifier appears in the final portion of the URI. Looking back at our previous example, the URIcontent://contacts/people/1/phone/3 returned a single phone number of type vnd.android.cursor.item/phone. If the URI had instead been content://contacts/people/1/phone, the application would instead return a list of all the phone numbers for the person having the person identifier number 1, and the MIME type of the data returned would be vnd.android.cursor.dir/phone.

As mentioned earlier, content providers can interpret the path portions of the URIs to suit their needs. This means the path portion can use items in the path to filter data to return to the caller. For instance, the built-in “media” content provider can return either internal or external data depending on whether the URI contains the word internal or external in the path.

The full CONTENT_URI for the simple Finch video is content://com.oreilly.demo.pa.finchvideo.SimpleFinchVideo/video.

The CONTENT_URI must be of type public static final Uri. It is defined in the FinchVideo class of our simple video application. In our public API class we start by extending the class BaseColumns, and then define a string named AUTHORITY:

public final class FinchVideo.SimpleVideos extends BaseColumns {

public static final String SIMPLE_AUTHORITY =

"com.oreilly.demo.pa.finchvideo.FinchVideo";

Then we define the CONTENT_URI itself:

public static final class FinchVideo.SimpleVideos implements BaseColumns {

public static final Uri CONTENT_URI =

Uri.parse("content://" + AUTHORITY + "/video");

Put more simply, defining this URI just involves picking an authority string that should use a Java package used by your application as the organizational identifier—a public API package is likely a better candidate here than an implementation package, as we discussed in Java Packages. The content provider identifier is just the name of your content provider class. The provider URI for our simple Finch video provider looks as follows:

"content://" + FinchVideo.FinchVideoContentProvider.SIMPLE_AUTHORITY + "/" +

FinchVideo.SimpleVideos.VIDEO

Creating the Column Names

Content providers exchange data with their clients in much the same way a SQL database exchanges data with database applications: using cursors full of rows and columns of data. A content provider must define the column names it supports just as database applications must define the columns they support. When the content provider uses a SQLite database as its data store, the obvious solution is to give the content provider columns with the same name as the database columns, and that’s just what SimpleFinchVideoContentProvider does. Because of this, no mapping is necessary between the SimpleFinchVideoContentProvider columns and the underlying database columns.

NOTE

Not all applications make all of their data available to content provider clients, and some more complex applications may want to make derivative views available to content provider clients. The projection map described in The SimpleFinchVideoContentProvider Class and Instance Variables is available to handle these complexities.

Declaring Column Specification Strings

The SimpleFinchVideoProvider columns are defined in the FinchVideo.SimpleVideos class discussed in this section. Every content provider must define an _id column to hold the record number of each row. The value of each _id must be unique within the content provider; it is the number that a client will append to the content provider’s vnd.android.cursor.item URI when attempting to query for a single record.

When the content provider is backed by a SQLite database, as is the case for SimpleFinchVideoProvider, the _id should have the type INTEGER PRIMARY KEY AUTOINCREMENT. This way, the rows will have a unique _id number and _id numbers will not be reused, even when rows are deleted. This helps support referential integrity by ensuring that each new row has an _id that has never been used before. If row _ids are reused, there is a chance that cached URIs could point to the wrong data.

Here is a complete program listing of the simple Finch video provider API, the class FinchVideo.SimpleVideos. Note that we have only included constants that serve the purposes we have outlined. We take care not to define content provider implementation constants here, since they will not be useful to a client and might tie clients to using a particular implementation of a content provider. We strive to achieve good software design and ensure that our software layers remain separable where clients should not have direct compilation dependencies on content provider implementation classes. The complete listing of the public API of the finch video provider API follows:

/**

* Simple Videos columns

*/

public class FinchVideo {

public static final class SimpleVideos implements BaseColumns {

// This class cannot be instantiated

private SimpleVideos() {}

// uri references all videos

public static final Uri VIDEOS_URI = Uri.parse("content://" +

SIMPLE_AUTHORITY + "/" + SimpleVideos.VIDEO);

/**

* The content:// style URL for this table

*/

public static final Uri CONTENT_URI = VIDEOS_URI;1

/**

* The MIME type of {@link #CONTENT_URI} providing a directory of notes.

*/

public static final String CONTENT_TYPE =

"vnd.android.cursor.dir/vnd.finch.video";2

/**

* The MIME type of a {@link #CONTENT_URI} sub-directory of a single

* video.

*/

public static final String CONTENT_VIDEO_TYPE =

"vnd.android.cursor.item/vnd.finch.video";

3

/**

* The video itself

* <P>Type: TEXT</P>

*/

public static final String VIDEO = "video";

/**

* Column name for the title of the video

* <P>Type: TEXT</P>

*/

public static final String TITLE = "title";

/**

* Column name for the description of the video.

*/

public static final String DESCRIPTION = "description";

/**

* Column name for the media uri

*/

public static final String URI = "uri";

/**

* Unique identifier for an element of media

*/

public static final String MEDIA_ID = "media_id";

}

...

// The API for FinchVideo.Videos is also defined in this class.

}

Here are some of the highlights of the code:

1

We use the VIDEOS_URI to define the value for our CONTENT_URI. The videos URI contains that content URI as described.

2

This is the MIME type of the video entries that our provider will store. In Implementing the getType Method we explain how our content provider uses this type.

3

These are the names of the columns that clients can use to access values in Cursor objects that our provider creates.

Writing and Integrating a Content Provider

Now that we’ve examined the general structure of the simple video list application, and provided a way for clients to access our content provider, it’s time to look at how the application both implements and consumes the SimpleFinchVideoContentProvider.

Common Content Provider Tasks

In the following sections, we provide a high-level guide to tasks associated with writing a content provider. We then provide an introduction to Android MVC and finish with an explanation of the SimpleFinchVideoContentProvider code.

Extending ContentProvider

Applications extend the ContentProvider class to handle URIs that refer to a particular type of data, such as MMS messages, pictures, videos, and so forth. For example, for a content provider class that handled videos, the ContentProvider.insert method would insert data that described a video into a SQLite table with columns appropriate for that information, such as a title, description, and similar information.

Start writing your content provider by implementing the following two methods:

onCreate

This method provides a hook to allow your content provider to initialize itself. Any code you want to run just once, such as making a database connection, should reside in this method.

String getType(Uri uri)

This method, given a URI, returns the MIME type of the data that this content provider provides at the given URI. The URI comes from the client application interested in accessing the data.

You’ll continue to implement by overriding the main content provider data access methods:

insert(Uri uri, ContentValues values)

This method is called when the client code needs to insert data into the database your content provider is serving. Normally, the implementation for this method will either directly or indirectly result in a database insert operation.

Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)

This method is called whenever a client wishes to read data from the content provider’s database. Normally, here, you retrieve data using an SQL SELECT statement and return a cursor containing the requested data. Developers call this method indirectly using Activity’s managedQuerymethod, or call startManagingQuery on the return values from this method. If your activity fails to “manage” the returned cursor, or fails to close the cursor, your application will contain a serious memory leak that will result in poor performance and, likely, crashes.

update(Uri uri, ContentValues values, String selection, String[] selectionArgs)

This method is called when a client wishes to update one or more rows in the content provider’s database. It translates to an SQL UPDATE statement.

delete(Uri uri, String selection, String[] selectionArgs)

This method is called when a client wishes to delete one or more rows in the content provider’s database. It translates to an SQL DELETE statement.

These four methods each perform an action on data referenced by a given URI parameter. A typical implementation of each of these methods starts with matching the incoming URI argument to a particular type of data. For example, a content provider implementation needs to figure out whether a given URI refers to a specific video, or to a group of videos. After a provider matches the URl, appropriate SQL operations follow. Each method then returns a value that either contains referenced data, describes affected data, or refers to the number of elements that were affected by the operation. For example, a query for a specific video would return a cursor that contained a single video element, if the given URI referenced a single element present in a local table.

Matching URIs to table data is an integral part of the job of a content provider. While you might not think it would be that hard to parse a content provider URI yourself, Android provides a nice utility for doing that job for you, which is convenient, but more importantly, helps developers to standardize on the format of provider URIs that we have discussed. The URIMatcher class supports mapping from URIs containing authority, path, and ID strings to application-defined constants usable with case statements that handle particular subtypes of URIs. From there, the provider can decide what SQL operations to use to manage actual table rows. A typical content provider will create a static instance of URIMatcher and populate it using a static initializer that calls URIMatcher.addURI to establish the first-level mapping used later in content provider data methods. Our simple video content provider does this in The SimpleFinchVideoContentProvider Class and Instance Variables.

File Management and Binary Data

Content providers often need to manage large chunks of binary data, such as a bitmap or music clip. Storage of large datafiles should influence the design of an application, and will likely have significant performance implications. A content provider can serve files through content provider URIs in a way that encapsulates the location of actual physical files so that clients can be agnostic about that information. Clients use content provider URIs to access files without knowing where the files actually reside. This layer of indirection enables a content provider to manage these files in a way that makes the most sense for the content provider data without having that information leak into the client—which could end up causing code changes in a client if the content provider needed to make a change in the way the physical files are stored. Generally, it’s much easier to change just the provider than all of its potential clients. Clients should not need to know that a set of provider media files might reside in flash memory, on the SD card, or entirely on the network, so long as the provider makes the files accessible from a set of content provider URIs that the client understands. The client will just use the method ContentResolver.openInputStream for a given URI and then read data from the resultant stream.

Additionally, when sharing large amounts of data between applications, since an Android application should not read or write files that another application has created, a content provider must be used to access the relevant bytes. Therefore, when the first content provider returns a pointer to a file, that pointer must be in the form of a content:// URI instead of a Unix filename. The use of a content:// URI causes the file to be opened and read under the permissions of the content provider that owns the file, not the client application (which should not have access rights to the file).

It’s also important to consider that filesystem I/O is much faster and more versatile than dealing with SQLite blobs, and it’s better to use the Unix filesystem to directly store binary data. Additionally, there’s no advantage to putting binary data in a database, since you can’t search on it!

To implement this approach in your app, the Android SDK documentation suggests one strategy where a content provider persists data to a file and stores a content:// URI in the database that points to the file, as shown in Figure 12-2. Client applications will pass the URI in this field toContentProvider.openStream to retrieve the byte stream from the file it specifies.

Android MVC’s typical use of cursors and content providers

Figure 12-2. Android MVC’s typical use of cursors and content providers

In detail, to implement the file approach, instead of creating a hypothetical user table like this:

CREATE TABLE user ( _id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, password

TEXT, picture BLOB );

the documentation suggests two tables that look like this:

CREATE TABLE user ( _id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, password

TEXT, picture TEXT );

CREATE TABLE userPicture ( _id INTEGER PRIMARY KEY AUTOINCREMENT,

_data TEXT );

The picture column of the user table will store a content:// URI that points to a row in the userPicture table. The _data column of the userPicture table will point to a real file on the Android filesystem.

If the path to the file were stored directly in the user table, clients would get a path but be unable to open the file, because it’s owned by the application serving up the content provider and the clients don’t have permission to read it. In the solution shown here, however, access is controlled by a ContentResolver class we’ll examine later.

The ContentResolver class looks for a column named _data when processing requests. If the file specified in that column is found, the provider’s openOutputStream method opens the file and returns a java.io.OutputStream to the client. This is the same object that would be returned if the client were able to open the file directly. The ContentResolver class is part of the same application as the content provider, and therefore is able to open the file when the client cannot.

Later in this chapter, we will demonstrate a content provider that uses the content provider file management facility to store thumbnail images.

Android MVC and Content Observation

It’s important to relate a bigger picture of how MVC works with content providers in Android. Additionally, a more detailed discussion of MVC in Android will lead us into A “Network MVC”.

In order to understand the power of the content provider framework, we need to discuss how cursor update events drive dynamic updates of Android UIs. We think it will help to highlight the often-overlooked communications pathways in the traditional MVC programming pattern, where the following occurs: the View delivers user input events to the Controller; the Controller makes modifications to the Model, and the Model sends update events to the View and to any other observer that registers interest in the Model; the View renders the contents of the Model, usually without directly engaging in application logic, and ideally, just simply iterates over the data in the Model.

In Android, the MVC pattern works as shown in Figure 12-3, where explicitly:

§ The Model consists of a content provider and the cursors it returns from its query method, as well as the data it holds in its SQLite tables.

§ Content providers should be written to send notification events whenever they change data by calling ContentResolver.notifyChange. Since the provider has sole access to modify the data, it will always know when data changes.

§ Notifications are delivered to a UI component, often a ListView, through observation of Cursor objects that are bound to content provider URIs. Cursor update messages fire from the Model to the View in response to the provider’s invocation of notifyChange. The View and Controller correspond to Android activities and their views, and to the classes that listen to the events they generate. Specifically, the system delivers ContentObserver.onChange messages to instances of ContentObserver registered using Cursor.registerContentObserver. The Android classes automatically register for cursor changes whenever a developer calls a method such as ListView.setAdapter(ListAdapter). The list view has an internal content observer, and the list adapter will register with the Cursor object.

Typical use of cursors and content providers in the Android MVC

Figure 12-3. Typical use of cursors and content providers in the Android MVC

To think about how this notification works in practice, suppose an activity were to call ContentResolver.delete. As we’ll see shortly, the corresponding content provider would first delete a row from its database, and then notify the content resolver URI corresponding to that row. Any listening cursors embedded in any view will be notified simply that data has changed; the views will, in turn, get the update event and then repaint themselves to reflect the new state. The views paint whatever state resides in their display area; if that happened to include the deleted element, it will disappear from the UI. The Cursor objects act as a proxy object between cursor consumers and the content provider system. Events flow from the provider, through the cursor, and into the View system. The degree of automation in this chain of events results in significant convenience for developers who need to perform only the minimum amount of work to put it into action. Additionally, programs don’t have to explicitly poll to keep their rendering of the model up-to-date since the model tells the view when state changes.

A Complete Content Provider: The SimpleFinchVideoContentProvider Code

Now that you understand the important tasks associated with writing a content provider and Android MVC—the communication system for Android content providers—let’s see how to build your own content provider. The SimpleFinchVideoContentProvider class extends ContentProvider as shown here:

public class SimpleFinchVideoContentProvider extends ContentProvider {

The SimpleFinchVideoContentProvider Class and Instance Variables

As usual, it’s best to understand the major class and instance variables used by a method before examining how the method works. The member variables we need to understand for SimpleFinchVideoContentProvider are:

private static final String DATABASE_NAME = "simple_video.db";

private static final int DATABASE_VERSION = 2;

private static final String VIDEO_TABLE_NAME = "video";

private DatabaseHelper mOpenHelper;

DATABASE_NAME

The name of the database file on the device. For the simple Finch video, the full path to the file is /data/data/com.oreilly.demo.pa.finchvideo/databases/simple_video.db.

DATABASE_VERSION

The version of the database that is compatible with this code. If this number is higher than the database version of the database itself, the application calls the DatabaseHelper.onUpdate method.

VIDEO_TABLE_NAME

The name of the video table within the simple_video database.

mOpenHelper

The database helper instance variable that is initialized during onCreate. It provides access to the database for the insert, query, update, and delete methods.

sUriMatcher

A static initialization block that performs initializations of static variables that can’t be performed as simple one-liners. For example, our simple video content provider begins by establishing a content provider URI mapping in a static initialization of a UriMatcher as follows:

private static UriMatcher sUriMatcher;

private static final int VIDEOS = 1;

private static final int VIDEO_ID = 2;

static {

sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

sUriMatcher.addURI(AUTHORITY, FinchVideo.SimpleVideos.VIDEO_NAME, VIDEOS);

// use of the hash character indicates matching of an id

sUriMatcher.addURI(AUTHORITY,

FinchVideo.SimpleVideos.VIDEO_NAME + "/#", VIDEO_ID);

...

// more initialization to follow

The UriMatcher class provides the basis of the convenience utilities that Android provides for mapping content provider URIs. To use an instance of it, you populate it with mappings from a URI string such as “videos” to a constant field. Our mappings work as follows: the application first provides an argument, UriMatcher.NO_MATCH, to the constructor of the provider UriMatcher to define the value that indicates when a URI does not match any URI. The application then adds mappings for multiple videos to VIDEOS, and then a mapping for a specific video to VIDEO_ID. With all provider URIs mapped to an integer value the provider can perform a switch operation to jump to the appropriate handling code for multiple and single videos.

This mapping causes a URI such as content://com.oreilly.demo.pa.finchvideo.SimpleFinchVideo/video to map to the constant VIDEOS, meaning all videos. A URI for a single video, such as content://oreilly.demo.pa.finchvideo.SimpleFinchVideo/video/7, will map to the constant VIDEO_ID for a single video. The hash mark at the end of the URI matcher binding is a wildcard for a URI ending with any integer number.

sVideosProjectionMap

The projection map used by the query method. This HashMap maps the content provider’s column names to database column names. A projection map is not required, but when used it must list all column names that might be returned by the query. In SimpleFinchVideoContentProvider, the content provider column names and the database column names are identical, so the sVideosProjectionMap is not required. But we provide it as an example for applications that might need it. In the following code, we create our example projection mapping:

// example projection map, not actually used in this application

sVideosProjectionMap = new HashMap<String, String>();

sVideosProjectionMap.put(FinchVideo.Videos._ID,

FinchVideo.Videos._ID);

sVideosProjectionMap.put(FinchVideo.Videos.TITLE,

FinchVideo.Videos.TITLE);

sVideosProjectionMap.put(FinchVideo.Videos.VIDEO,

FinchVideo.Videos.VIDEO);

sVideosProjectionMap.put(FinchVideo.Videos.DESCRIPTION,

FinchVideo.Videos.DESCRIPTION);

Implementing the onCreate Method

During initialization of the simple Finch video content provider, we create the video’s SQLite data store as follows:

private static class DatabaseHelper extends SQLiteOpenHelper {

public void onCreate(SQLiteDatabase sqLiteDatabase) {

createTable(sqLiteDatabase);

}

// create table method may also be called from onUpgrade

private void createTable(SQLiteDatabase sqLiteDatabase) {

String qs = "CREATE TABLE " + VIDEO_TABLE_NAME + " (" +

FinchVideo.SimpleVideos._ID + " INTEGER PRIMARY KEY, " +

FinchVideo.SimpleVideos.TITLE_NAME + " TEXT, " +

FinchVideo.SimpleVideos.DESCRIPTION_NAME + " TEXT, " +

FinchVideo.SimpleVideos.URI_NAME + " TEXT);";

sqLiteDatabase.execSQL(qs);

}

}

When creating SQLite tables to support content provider operations, developers are required to provide a field with a primary key called _id. While it’s not immediately clear that this field is required, unless you read the Android developer docs in detail, the Android content management system actually does enforce the presence of the _id field in the cursors that are returned by the query method. _id is used in query matching with the # special character in content provider URLs. For example, a URL such as content://contacts/people/25 would map to a data row in a contactstable with _id 25. The requirement is really just to use a specific name for a table primary key.

Implementing the getType Method

Next, we implement the getType method to determine MIME types of arbitrary URIs passed from the client. As you can see in the following code, we provide URI matching for VIDEOS, and VIDEO_ID to MIME types we defined in our public API:

public String getType(Uri uri) {

switch (sUriMatcher.match(uri)) {

case VIDEOS:

return FinchVideo.SimpleVideos.CONTENT_TYPE;

case VIDEO_ID:

return FinchVideo.SimpleVideos.CONTENT_VIDEO_TYPE;

default:

throw new IllegalArgumentException("Unknown video type: " + uri);

}

}

Implementing the Provider API

A content provider implementation must override the data methods of the ContentProvider base class: insert, query, update, and delete. For the simple video application, these methods are defined by the SimpleFinchVideoContentProvider class.

The query method

After matching the incoming URI, our content provider query method performs a corresponding select on a readable database, by delegating to SQLiteDatabase.query, and then returns the results in the form of a database Cursor object. The cursor will contain all database rows described by the URI argument. After we’ve made the query, the Android content provider mechanism automatically supports the use of cursor instances across processes, which permits our provider query method to simply return the cursor as a normal return value to make it available to clients that might reside in another process.

The query method also supports the parameters uri, projection, selection, selectionArgs, and sortOrder, which are used in the same manner as the arguments to SQLiteDatabase.query that we saw in Chapter 10. Just as with any SQL SELECT, parameters to the query method enable our provider clients to select only specific videos that match the query parameters. In addition to passing a URI, a client calling the simple video content provider could also pass an additional where clause with where arguments. For example, these arguments would enable a developer to query for videos from a particular author.

NOTE

As we’ve seen, MVC in Android relies on cursors and the data they contain, as well as framework-based delivery of content observer update messages. Since clients in different processes share Cursor objects, a content provider implementation must take care not to close a cursor that it has served from its query method. If a cursor is closed in this manner, clients will not see exceptions thrown; instead, the cursor will always act like it is empty, and it will no longer receive update events—it’s up to the activity to properly manage the returned cursors.

When the database query completes, our provider then calls Cursor.setNotificationUri to set the URI that the provider infrastructure will use to decide which provider update events get delivered to the newly created cursor. This URI becomes the point of interaction between clients that observe data referenced by that URI and the content provider that notifies that URI. This simple method call drives the content provider update messages that we discussed in Android MVC and Content Observation.

Here, we provide the code for our simple content provider’s query method, which performs URI matching, queries the database, and then returns the cursor:

@Override

public Cursor query(Uri uri, String[] projection, String where,

String[] whereArgs, String sortOrder)

{

// If no sort order is specified use the default

String orderBy;

if (TextUtils.isEmpty(sortOrder)) {

orderBy = FinchVideo.SimpleVideos.DEFAULT_SORT_ORDER;

} else {

orderBy = sortOrder;

}

int match = sUriMatcher.match(uri);1

Cursor c;

switch (match) {

case VIDEOS:

// query the database for all videos

c = mDb.query(VIDEO_TABLE_NAME, projection,

where, whereArgs,

null, null, sortOrder);

c.setNotificationUri(

getContext().getContentResolver(),

FinchVideo.SimpleVideos.CONTENT_URI);2

break;

case VIDEO_ID:

// query the database for a specific video

long videoID = ContentUris.parseId(uri);

c = mDb.query(VIDEO_TABLE_NAME, projection,

FinchVideo.Videos._ID + " = " + videoID +

(!TextUtils.isEmpty(where) ?

" AND (" + where + ')' : ""),

whereArgs, null, null, sortOrder);

c.setNotificationUri(

getContext().getContentResolver(),

FinchVideo.SimpleVideos.CONTENT_URI);

break;

default:

throw new IllegalArgumentException("unsupported uri: " + uri);

}

return c;3

}

Here are some of the highlights of the code:

1

This matches the URI using our prebuilt URI matcher.

2

Setting the notification URI to FinchVideo.SimpleVideos.CONTENT_URI causes the cursor to receive all content resolver notification events for data referenced by that URI. In this case, the cursor will receive all events related to all videos, since that is whatFinchVideo.SimpleVideos.CONTENT_URI references.

3

The cursor is returned directly. As mentioned, the Android content provider system provides support for sharing any data in the cursor across processes. Interprocess data sharing happens “for free” as part of the content provider system; you can just return the cursor and it will become available to activities in different processes.

The insert method

Let’s move on to the insert method , which receives values from a client, validates them, and then adds a new row to the database containing those values. The values are passed to the ContentProvider class in a ContentValues object:

@Override

public Uri insert(Uri uri, ContentValues initialValues) {

// Validate the requested uri

if (sUriMatcher.match(uri) != VIDEOS) {

throw new IllegalArgumentException("Unknown URI " + uri);

}

ContentValues values;

if (initialValues != null) {

values = new ContentValues(initialValues);

} else {

values = new ContentValues();

}

verifyValues(values);

// insert the initialValues into a new database row

SQLiteDatabase db = mOpenDbHelper.getWritableDatabase();

long rowId = db.insert(VIDEO_TABLE_NAME,

FinchVideo.SimpleVideos.VIDEO_NAME, values);

if (rowId > 0) {

Uri videoURi =

ContentUris.withAppendedId(

FinchVideo.SimpleVideos.CONTENT_URI, rowId);1

getContext().getContentResolver().

notifyChange(videoURi, null);2

return videoURi;

}

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

}

The insert method will also match the incoming URI, perform a corresponding database insert operation, and then return a URI that references the new database row. Since the SQLiteDatabase.insert method returns the database row ID of the newly inserted row, which is also its value for the_id field, the content provider can easily put together the right URI by appending the rowID variable to the content provider authority defined in the content provider public API that we discussed in Chapter 3.

Here are some of the highlights of the code:

1

We use Android’s utilities for manipulating content provider URIs—specifically, the method ContentUris.withAppendedId to append the rowId as the ID of the returned insertion URI. Clients can turn around and query the content provider using this same URI to select a cursor containing the data values for the inserted row.

2

Here the content provider notifies a URI that will cause a content update event to be fired and delivered to observing cursors. Note that the provider’s invocation of notify is the only reason an event will be sent to content observers.

The update method

The update method operates in the same manner as insert, but instead calls update on the appropriate database to change database rows that the URI references. The update method returns the number of rows affected by the update operation:

@Override

public int update(Uri uri, ContentValues values, String where,

String[] whereArgs)

{

// the call to notify the uri after deletion is explicit

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

SQLiteDatabase db = mOpenDbHelper.getWritableDatabase();

int affected;

switch (sUriMatcher.match(uri)) {

case VIDEOS:

affected = db.update(VIDEO_TABLE_NAME, values,

where, whereArgs);

break;

case VIDEO_ID:

String videoId = uri.getPathSegments().get(1);

affected = db.update(VIDEO_TABLE_NAME, values,

FinchVideo.SimpleVideos._ID + "=" + videoId

+ (!TextUtils.isEmpty(where) ?

" AND (" + where + ')' : ""),

whereArgs);

break;

default:

throw new IllegalArgumentException("Unknown URI " + uri);

}

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

return affected;

}

The delete method

The delete method is similar to update, but will delete rows referenced by the given URI. Like update, delete returns the number of rows affected by the delete operation:

@Override

public int delete(Uri uri, String where, String[] whereArgs) {

int match = sUriMatcher.match(uri);

int affected;

switch (match) {

case VIDEOS:

affected = mDb.delete(VIDEO_TABLE_NAME,

(!TextUtils.isEmpty(where) ?

" AND (" + where + ')' : ""),

whereArgs);

break;

case VIDEO_ID:

long videoId = ContentUris.parseId(uri);

affected = mDb.delete(VIDEO_TABLE_NAME,

FinchVideo.SimpleVideos._ID + "=" + videoId

+ (!TextUtils.isEmpty(where) ?

" AND (" + where + ')' : ""),

whereArgs);

// the call to notify the uri after deletion is explicit

getContext().getContentResolver().

notifyChange(uri, null);

break;

default:

throw new IllegalArgumentException("unknown video element: " +

uri);

}

return affected;

}

Note that the preceding descriptions relate only to our simple implementation of a content provider; more involved scenarios could involve joining across tables for a query or cascaded deletes for deleting a given data item. The content provider is free to pick its own scheme for data management using the Android SQLite API so long as it does not break the content provider client API.

Determining How Often to Notify Observers

As we’ve seen from our listing of the content provider data management operations, notification does not happen for free in the Android content management system: an insert into a SQLite table does not automatically set up a database trigger that fires notification on behalf of a content provider. It’s up to the developer of the provider to implement a scheme that determines the appropriate time to send notifications and decides which URIs to send when content provider data changes. Usually content providers in Android send notifications immediately for all URIs that have changed during a particular data operation.

When designing a notification scheme, a developer should consider the following trade-off: fine-grained notification results in more precise change updates that can reduce load on the user interface system. If a list is told a single element has changed, it can decide to repaint only that element if it happens to be visible. But fine-grained notification also has the drawback that more events get pushed through the system. The UI will likely repaint more times since it will be getting more individual notification events. Coarse-grained notification runs fewer events through the system, but running fewer events often means that the UI will have to repaint more of itself on receiving notification. For example, a list could receive a single event directing it to update all elements when only three individual elements had actually changed. We suggest keeping this trade-off in mind when picking a notification scheme. For example, you might consider waiting until you finish reading a large number of events and then firing a single “everything changed” event, rather than sending an update for each event.

Often, content providers simply notify clients of whatever URIs were involved when data changes.

Declaring Your Content Provider

In Using a content provider we saw how clients access and use a content provider. Now that we have our own simple content provider, all that is left is to make it available to clients by adding the following line of XML to your AndroidManifest.xml:

<provider android:name=".provider.SimpleFinchVideoContentProvider"

android:authorities="oreilly.demo.pa.finchvideo.SimpleFinchVideo"/>

After you have built your application, its .apk file contains the provider implementation classes, and its manifest file contains a line similar to the line of XML we just added, all application code on the Android platform will be able to access it, assuming it has requested and been granted permission to do so, as described in Chapter 3.

Having completed the task of creating your own simple content provider in this chapter, it’s time to look into some novel content provider patterns, which we’ll do in Chapter 13.