To explore this topic, we’ll create a database that contains a collection of books. The book database contains only one table called books, and its columns are name, isbn, and
author. You’ll define this sort of relevant metadata in a Java class. This metadata- bearing Java class BookProviderMetaData is shown in Listing 3–24. Some key elements of this metadata class are highlighted.
Listing 3–24. Defining Metadata for Your Database: The BookProviderMetaData Class public class BookProviderMetaData
{
public static final String AUTHORITY = "com.androidbook.provider.BookProvider";
public static final String DATABASE_NAME = "book.db"; public static final int DATABASE_VERSION = 1;
public static final String BOOKS_TABLE_NAME = "books";
private BookProviderMetaData() {} //inner class describing BookTable
public static final class BookTableMetaData implements BaseColumns {
private BookTableMetaData() {}
public static final String TABLE_NAME = "books"; //uri and MIME type definitions
public static final Uri CONTENT_URI =
Uri.parse("content://" + AUTHORITY + "/books"); public static final String CONTENT_TYPE =
"vnd.android.cursor.dir/vnd.androidbook.book"; public static final String CONTENT_ITEM_TYPE =
"vnd.android.cursor.item/vnd.androidbook.book"; public static final String DEFAULT_SORT_ORDER = "modified DESC";
//Additional Columns start here. //string type
public static final String BOOK_NAME = "name"; //string type
public static final String BOOK_ISBN = "isbn"; //string type
public static final String BOOK_AUTHOR = "author"; //Integer from System.currentTimeMillis()
public static final String CREATED_DATE = "created"; //Integer from System.currentTimeMillis()
public static final String MODIFIED_DATE = "modified"; }
}
This BookProviderMetaData class starts by defining its authority to be
com.androidbook.provider.BookProvider. We are going to use this string to register the provider in the Android manifest file. This string forms the front part of the URIs intended for this provider.
This class then proceeds to define its one table (books) as an inner BookTableMetaData class. The BookTableMetaData class then defines a URI for identifying a collection of books. Given the authority in the previous paragraph, the URI for a collection of books will look like this:
content://com.androidbook.provider.BookProvider/books This URI is indicated by the constant
BookProviderMetaData.BookTableMetaData.CONTENT_URI
The BookTableMetaData class then proceeds to define the MIME types for a collection of books and a single book. The provider implementation will use these constants to return the MIME types for the incoming URIs.
BookTableMetaData then defines the set of columns: name, isbn, author, created (creation date), and modified (last-updated date).
NOTE: You should point out your columns’ data types through comments in the code.
The metadata class BookTableMetaData also inherits from the BaseColumns class that provides the standard _id field, which represents the row ID. With these metadata definitions in hand, we’re ready to tackle the provider implementation.
Extending ContentProvider
Implementing our BookProvider sample content provider involves extending the ContentProvider class and overriding onCreate() to create the database and then implement the query, insert, update, delete, and getType methods. This section covers the setup and creation of the database, while the following sections deal with each of the individual methods: query, insert, update, delete, and getType.
A query method requires the set of columns it needs to return. This is similar to a select clause that requires column names along with their as counterparts (sometimes called synonyms). Android uses a map object that it calls a projection map to represent these column names and their synonyms. We will need to set up this map so we can use it later in the query-method implementation. In the code for the provider implementation (see Listing 3–25), you will see this done up front.
Most of the methods we’ll be implementing take a URI as an input. Although all the URIs that this content provider is able to respond to start with the same pattern, the tail ends of the URIs will be different—just like a web site. Each URI, although it starts the same, must be different to identify different data or documents. Let us illustrate this with an example:
Uri1: content://com.androidbook.provider.BookProvider/books Uri2: content://com.androidbook.provider.BookProvider/books/12
See how the Book Provider needs to distinguish each of these URIs. This is a simple case. If our book provider had been housing more objects rather than just books, then there would be more URIs to identify those objects.
The provider implementation needs a mechanism to distinguish one URI from the other; Android uses a class called UriMatcher for this work. So we need to set up this object with all our URI variations. You will see this code in Listing 3–25 after the segment that creates a projection map. We’ll further explain the UriMatcher class in the section “Using UriMatcher to Figure Out the URIs,” but for now, know that the code shown here allows the content provider to identify one URI vs. the other.
And finally, the code in Listing 3–25 overrides the onCreate() method to facilitate the database creation. We have demarcated the code with highlighted comments to reflect the three areas we have talked about here:
Setting up a column projection Setting up the UriMatcher Creating the database
Listing 3–25. Implementing the BookProvider Content Provider public class BookProvider extends ContentProvider {
//Create a Projection Map for Columns
//Projection maps are similar to "as" construct in an sql //statement whereby you can rename the
//columns.
private static HashMap<String, String> sBooksProjectionMap; static
{
sBooksProjectionMap = new HashMap<String, String>();
sBooksProjectionMap.put(BookTableMetaData._ID, BookTableMetaData._ID);
//name, isbn, author
sBooksProjectionMap.put(BookTableMetaData.BOOK_NAME , BookTableMetaData.BOOK_NAME); sBooksProjectionMap.put(BookTableMetaData.BOOK_ISBN , BookTableMetaData.BOOK_ISBN); sBooksProjectionMap.put(BookTableMetaData.BOOK_AUTHOR , BookTableMetaData.BOOK_AUTHOR);
//created date, modified date
sBooksProjectionMap.put(BookTableMetaData.CREATED_DATE
, BookTableMetaData.CREATED_DATE); sBooksProjectionMap.put(BookTableMetaData.MODIFIED_DATE
, BookTableMetaData.MODIFIED_DATE); }
//Provide a mechanism to identify all the incoming uri patterns. private static final UriMatcher sUriMatcher;
private static final int INCOMING_BOOK_COLLECTION_URI_INDICATOR = 1; private static final int INCOMING_SINGLE_BOOK_URI_INDICATOR = 2; static {
sUriMatcher.addURI(BookProviderMetaData.AUTHORITY , "books" , INCOMING_BOOK_COLLECTION_URI_INDICATOR); sUriMatcher.addURI(BookProviderMetaData.AUTHORITY , "books/#", INCOMING_SINGLE_BOOK_URI_INDICATOR); }
// Deal with OnCreate call back
private DatabaseHelper mOpenHelper; @Override
public boolean onCreate() {
mOpenHelper = new DatabaseHelper(getContext()); return true;
}
private static class DatabaseHelper extends SQLiteOpenHelper { DatabaseHelper(Context context) {
super(context, BookProviderMetaData.DATABASE_NAME, null , BookProviderMetaData.DATABASE_VERSION); }
//Create the database @Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + BookTableMetaData.TABLE_NAME + " (" + BookProviderMetaData.BookTableMetaData._ID
+ " INTEGER PRIMARY KEY,"
+ BookTableMetaData.BOOK_NAME + " TEXT," + BookTableMetaData.BOOK_ISBN + " TEXT," + BookTableMetaData.BOOK_AUTHOR + " TEXT," + BookTableMetaData.CREATED_DATE + " INTEGER," + BookTableMetaData.MODIFIED_DATE + " INTEGER" + ");"); }
//Deal with version changes @Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Log.w(TAG, "Upgrading database from version " + oldVersion + " to " + newVersion + ", which will destroy all old data"); db.execSQL("DROP TABLE IF EXISTS " + BookTableMetaData.TABLE_NAME); onCreate(db);
} }