Working with Content Provider classes

A ContentProvider is used in Android to share data between different applications. We have already discussed the fact that each application runs in its own process (normally), and data and files stored there are not accessible by other applications by default. We have explained that you can make preferences and files available across application boundaries with the correct permissions and if each application knows the context/path. Nevertheless, that is a limited solution for related applications that already know details about one another. In contrast, with a ContentProvider you can publish and expose a particular data type for other applications to use to query, add, update, and delete, and those applications don't need to have any prior knowledge of paths or resources or even know who or what is providing the content.

The canonical ContentProvider example in Android is the contacts list—the list of name, address, and phone information stored in the phone. You can access this data from any application using a specific URI, content://contacts/people/, and a series of methods provided by the Activity and ContentResolver classes to retrieve and store data. You will learn more about ContentResolver as we explore provider details. One other data-related concept that a ContentProvider brings along with it is the Cursor, the same object we used previously when dealing with SQLite database result sets. Cursor is also returned by the provider query methods you will learn about shortly.

In this section we are going to build several small sample applications to help us look at all of the ContentProvider angles. First we will build a single Activity-based application, which we are calling ProviderExplorer, that will work with the built-in contacts database to query, add, update, and delete data. Then we will create another application that implements its own ContentProvider and includes a similar explorer-type

ContentProvider leaks a Cursor

Returning a Cursor is one of the quirks of a ContentProvider. Exposing a Cursor from a ContentProvider is a fairly "leaky" abstraction, and it makes for an inconsistent API, as you shall learn. Cursor is part of the android.database package, which implies you are working with database records and binds you to certain database concepts when you get results. Yet the entire idea behind a ContentProvider is supposed to be that it is backend agnostic. That is to say you should be able to implement a ContentProvider and not use a database to get and store data within it if the situation warrants (the current Android documentation contradicts itself on this point; in one place it says not using a database is possible, and in another it says it is not). Currently, regardless of the merits or demerits, you will need to learn to deal with Cursor-based results and SQL constructs when working with ContentProvider calls.

Activity to manipulate that data as well. Along with covering these fundamentals, we will discuss other built-in providers on the platform beyond contacts.

The ProviderExplorer application we are going to build here will ultimately have one large scrollable screen, which is depicted in figure 5.5. Keep in mind that we are focusing on covering all the bases in one Activity—exposing all of the ContentProvider

Activity to manipulate that data as well. Along with covering these fundamentals, we will discuss other built-in providers on the platform beyond contacts.

The ProviderExplorer application we are going to build here will ultimately have one large scrollable screen, which is depicted in figure 5.5. Keep in mind that we are focusing on covering all the bases in one Activity—exposing all of the ContentProvider

Figure 5.5 ProviderExplorer sample application that uses the contact's ContentProvider

operations in a single place—rather than on aesthetics or usability (this application is downright ugly, but that's intentional—at least this time).

To begin we will explore the syntax of URIs and the combinations and paths used to perform different types of operations with the ContentProvider and Content-Resolver classes.

5.4.1 Understanding URi representations and manipulating records

Each ContentProvider is required to expose a unique CONTENT_URI that is used to identify the content type it will handle. This URI is used in one of two forms, singular or plural, as shown in table 5.1, to query data.

Table 5.1 ContentProvider URI variations for different purposes

URI

Purpose

content://contacts/people/ content://contacts/people/1

Return List of all people from provider registered to handle content://contacts

Return or manipulate single person with ID 1 from provider registered to handle content://contacts

The URI concept comes into play whether or not you are querying data or adding or deleting it, as you shall see. To get familiar with this process we will take a look at the basic CRUD data-manipulation methods and see how they are carried out with the contacts database and respective URIs.

We will step through each task to highlight the details: create, read, update, and delete. To do this concisely we will build one Activity in the ProviderExplorer example application that performs all of these actions. In the next few sections we will take a look at different parts of this Activity to focus on each task.

The first thing we need to do is set up a bit of scaffolding for the contacts provider we will be using; this is done in the first portion of listing 5.12, the start of the ProviderExplorer class.

Listing 5.12 Start of Activity that sets up needed inner classes public class ProviderExplorer extends Activity {

private EditText addName; private EditText addPhoneNumber; private EditText editName; private EditText editPhoneNumber; private Button addContact; private Button editContact;

private long contactId; o Include inner private class Contact { <-1 Contact bean public long id; public String name; public String phoneNumber;

public Contact(long id, String name, String phoneNumber) { Download at Boykma.Com

this.phoneNumber = phoneNumber;

@Override public String toString() {

return this.name + "\n" + this.phoneNumber;

C Extend Button with private class ContactButton extends Button { <1— public Contact contact;

public ContactButton(Context ctx, Contact contact) { super(ctx);

this.contact = contact;

ContactButton

@Override public void onCreate(Bundle icicle) { super.onCreate(icicle);

this.setContentView(R.layout.provider_explorer);

this.addName = (EditText) this.findViewById(R.id.add_name); this.addPhoneNumber =

(EditText) this . findViewById(R.id.add_phone_number); this.editName =

(EditText) this . findViewById(R.id.edit_name); this.editPhoneNumber =

(EditText) this.findViewById(R.id.edit_phone_number);

this.addContact =

(Button) this.findViewById(R.id.add_contact_button); this.addContact.setOnClickListener (new OnClickListener() { public void onClick(final View v) {

ProviderExplorer.this.addContact(); <—i

Call addContact Q and editContact

this.editContact =

(Button) this.findViewById(R.id.edit_contact_button); this.editContact.setOnClickListener(new OnClickListener() { public void onClick(final View v) {

ProviderExplorer.this.editContact(); <—1

D Create anonymous click listeners

To start out the ProviderExplorer Activity we are creating a simple inner bean class to represent a Contact record (this is not a comprehensive representation, but it does capture the fields we are interested in here) O. Then we include another inner class to represent a ContactButton ©. This class extends Button and includes a reference to a particular Contact.

After we have the add and edit buttons established, we create anonymous OnClickListener implementations D that call the respective add and edit methods when a button is clicked Q.

That rounds out the setup-related tasks for ProviderExplorer. The next thing we need to implement is the onStart method, which adds more buttons dynamically for populating edit data and deleting data. This is shown in listing 5.13.

Listing 5.13 onStart portion of the ProviderExplorer Activity

@Override public void onStart () {

List<Contact> contacts = this .getContacts () ; <-1 contacts

LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(200, android, view. ViewGroup . LayoutParams . WRAP_CONTENT);

LinearLayout editLayout = (LinearLayout)

this.findViewById(R.id.edit_buttons_layout); LinearLayout deleteLayout = (LinearLayout) this.findViewById(R.id.delete_buttons_layout) params.setMargins(10, 0, 0, 0);

Create C dynamic _| layouts for (Contact c : contacts) {

ContactButton contactEditButton =

contactEditButton.setText(c.toString()); editLayout.addView(contactEditButton, params); contactEditButton.setOnClickListener(new OnClickListener() { public void onClick(final View v) {

ContactButton view = (ContactButton) v; editName.setText(view.contact.name); editPhoneNumber.setText(view.contact.phoneNumber); contactId = view.contact.id; } Create dynamic ©

ContactButton contactDeleteButton =

contactDeleteButton.setText("Delete " + c.name); deleteLayout.addView(contactDeleteButton, params); contactDeleteButton.setOnClickListener (new OnClickListener() { public void onClick(final View v) {

ContactButton view = (ContactButton) v; contactId = view.contact.id; deleteContact();

LinearLayout layout = (LinearLayout) this.findViewById(R.id.edit_buttons_layout); TextView empty = new TextView(this); empty.setText("No current contacts");

layout.addView(empty, params);

The onStart method makes a call to the getContacts method O. This method, which you will see in listing 5.14, returns a List of current Contact objects from the Android contacts database. Once we have the current contacts, we loop through them and dynamically create a layout in code for edit and delete, respectively ©. After we have the layout within it, we create a few view objects, including a ContactButton to populate an edit form and a button to delete a record ©. Each button is then manually added to its respective LinearLayout we have referenced through R.java.

Once our onStart method is in place, we have a View to display all the current contacts and all of the buttons, static and dynamic, that we need in order to be able to add, edit, and delete contact data. From there we need to implement the methods to perform these actions—this is where we will use a ContentResolver and other related classes.

Initially we need to populate our display of current contacts, and to do that we need to query for (read) data. QUERYING DATA

The Activity class has a managedQuery method that is used to make calls into registered ContentProvider classes. When we create our own ContentProvider in section 5.5.3, we will show how a provider is registered with the platform; for now we are going to focus on calling existing providers. Each provider is required to advertise (or publish, in Android terms) the CONTENT_URI it supports. To query the contacts provider, as we are doing in listing 5.14, we have to know this URI and then get a Cursor by calling managedQuery.

Listing 5.14 Query details for ContentProvider in the ProviderExplorer Activity

Make projection private List<Contact> getContacts () { List<Contact> results = null; long id = 0L; String name = null; String phoneNumber = null; String [] projection = new String [] { Contacts.People ._ID, Contacts.People.NAME, Contacts.People.NUMBER } ; ContentResolver resolver = this.getContentResolver(); < Cursor cur = resolver. query (Contacts . People . CONTENT_URI, projection, null, null,

Contacts . People . DEFAULT_SORT_ORDER) ; <J

results = new ArrayList<Contact>();

ContentResolver reference e

Get Cursor from resolver

I Walk results and © populate data id = cur.getLong(cur.getColumnIndex(BaseColumns._ID)); name = cur.getString(cur.getColumnIndex(PeopleColumns.NAME))¡ phoneNumber =

cur.getString(cur.getColumnIndex(PhonesColumns.WUMBER));

results.add(new Contact(id, name, phoneNumber)) ;

return results;

The Android contacts database is really a composite of several types of data. A contact includes details of a person (name, company, photo, and the like), one or more phone numbers (each of which has a number, type, label, and such), and other information. A ContentProvider typically supplies all the details of the URI and the types it supports as constants in a class. In the android.provider package, there is Contacts class that corresponds to the contacts provider. This class has nested inner classes that represent People and Phones. In additional inner classes in those, there are constants that represent fields or columns of data for each type. This structure with all the inner classes can be mind bending at times, but the bottom line is that Contacts data ends up in multiple tables, and the values you need to query and manipulate this data come from the inner classes for each type.

The columns we will be using to get and set data are defined in these classes. Here we are going to work with only the people and phones parts of contacts. We start by creating a projection of the columns we want to return as a String array O. Then we get a reference to the ContentResolver we will use ©. Using the resolver, we obtain a Cursor object ©. Once we have the Cursor, which represents the rows in the data we have returned, we iterate over it to create our contact objects ©.

Managed Cursor

To obtain a Cursor reference you can also use the managedQuery method of the Activity class. A managed Cursor is automatically cleaned up when your Activity pauses, and it is also restarted when it starts. It is a Cursor instance that has its state maintained by the platform in conjunction with the Activity lifecycle. This is very helpful, in most cases. If you just need to retrieve data within an Activity, you will want to use a managed Cursor as opposed to a ContentResolver. (We are not using one in the last example, because there we need to do more than retrieve data, and we want to focus on the provider/resolver components.)

The query method on the ContentResolver class also lets you pass in additional arguments to narrow the results. Specifically, where we passed null, null in listing 5.14, you can alternatively pass in a filter to narrow the rows you want to return in the form of an SQL WHERE clause and optional replacement tokens for that Where clause (injected at each ?). This is somewhat typical SQL usage, so it's easy to work with. The downside comes when you aren't using a database to back your ContentProvider. This is where the abstraction leaks like a sieve—though it might be possible to not use a database for a data source, you still have to handle SQL statements in your provider implementation, and you must require that anyone who uses your provider also has to deal with SQL constructs.

Now that we have covered how to query for data to return results, we look at inserting new data—adding a row. INSERTING DATA

In listing 5.15 we show the next part of the ProviderExplorer class, the addContact method. This is used with the add form elements in our Activity to insert a new row of data into the contacts-related tables.

Listing 5.15 Insert details for ContentProvider in the ProviderExplorer Activity private void addContact () {

ContentResolver resolver = this . getContentResolver () ; <3-1 Get

ContentValues values = new ContentValues () ; <1-

ContentResolver V handle

Use ContentValues

values.put(Contacts.People.NAME, this.addName.getText().toString());

Uri personUri = C for query values

Contacts.People.createPersonlnMyContactsGroup (

resolver, values); < | Use Contacts helper values .clear () ; D to create person

Uri phoneUri = Uri. withAppendedPath(personUri,

Contacts . People . Phones . CONTENT_DIRECTORY) ; <-1 Append person values .put (Contacts . Phones . TYPE, Phones . TYPE_MOBILE); Q Uri for phone Uri values.put(Contacts.Phones.NUMBER, this.addPhoneNumber.getText().toString());

resolver. insert (phoneUri, values); <—Q Insert data using resolver this . startActivity(new Intent(this, ProviderExplorer.class));

The first thing to see in the addContact method is that we are getting a ContentResolver reference O and using a ContentValues object to map column names with values C. This is an Android-specific type of map object. After we have our variables in place, we use the special createPersonInMyContactsGroup helper method on the Contacts.People class to both insert a record and return the Uri Q. This method uses the resolver for us, under the covers, and performs an insert. The Contacts class structure has a few helper methods sprinkled throughout (see the Javadocs). These are used to cut down on the amount of code you have to know and write to perform common tasks, such as adding a contact to the My Contacts group (the built-in group that the phone displays by default in the contacts app).

After we have created a new contact People record, we append new data to that existing Uri in order to create a phone record associated with the same person Q. This is a nice touch that the API provides. You can often append and/or build onto an existing Uri in order to access different aspects of the data structure. After we have the Uri and have reset and updated the values object, we directly insert a phone record this time, using the ContentResolver insert method (no helper for this one) Q.

After adding data, we need to look at how to update or edit existing data.

UPDATING DATA

To update a row of data you first obtain a Cursor row reference to it and then use the update-related Cursor methods. This is shown in listing 5.16.

Listing 5.16 Update details for ContentProvider in the ProviderExplorer Activity private void editContact () {

ContentResolver resolver = this.getContentResolver(); ContentValues values = new ContentValues();

Uri personUri = Contacts . People . CONTENT_URI .buildUpon() O Append to an . appendPath (Long. toString(this . contact Id) ) ,build(); <1-' eXisting Uri values, put (Contacts. People. NAME, C Update valUes this.editName.getText().toString()); resolver. update (personUri, values, null, null); <1-1 Call

C Update values <_I to change data

© resolver.update

values.clear(); Uri phoneUri = Uri. withAppendedPath(personUri,

Contacts . People . Phones . CONTENT_DIRECTORY + "/1"); <-1 After updated values .put (Contacts . Phones .NUMBER, © get Uri this.editPhoneNumber.getText().toString()); resolver.update(phoneUri, values, null, null);

this.startActivity(new Intent(this, ProviderExplorer.class));

In updating data, we start with the standard People.CONTENT_URI and append a specific ID path to it using UriBuilder O- UriBuilder is a very helpful class that uses the builder pattern to allow you to construct and access the components of a Uri object. After we have the URI ready, we update the values data C and call resolver.update to make the update happen ©. As you can see, the update process when using a ContentResolver is pretty much the same as the create process—with the noted exception that the update method allows you to again pass in a WHERE clause and replacement tokens (SQL style).

For this example, after we have updated the person's name, we need to once again obtain the correct Uri to also update the associated phone record. We do this by again appending additional Uri path data to an object we already have, and we slap on the specific ID we want ©. Outside of example purposes there would be more work to do here in order to determine which phone record for the contact needs to be updated (here we are using ID 1 as a shortcut).

Although we are updating only single records based on a specific URI, keep in mind that you can update a set of records using the nonspecific form of the URI and the WHERE clause.

Lastly, in our look at manipulating data through a ContentProvider, we need to implement our delete method. DELETING DATA

To delete data we will return to the ContentResolver object we used to insert data. This time we will call the delete method, as seen in listing 5.17.

Listing 5.17 Delete details for ContentProvider in the ProviderExplorer Activity private void deleteContact() {

Uri personUri = Contacts . People . CONTENT_URI;

personUri = personUri .buildUpon () . O Use UriBuilder appendPath(Long.toString(contactId) ) .build() ; <-1 to append path getContentResolver (). delete (personUri, null, null); <1—

startActivity(new Intent(this, ProviderExplorer.class)); } } Call getContentResolver.delete ©

The delete concept is pretty simple, once you have the rest of the process in hand. Again we use the UriBuilder approach to set up a Uri for a specific record O, and then we obtain a ContentResolver reference, this time inline with our delete method call C.

What if the content changes after the fact?

When you use a ContentProvider, which by definition is accessible by any application on the system, and you make a query, you are getting only the current state of the data back. The data could change after your call, so how do you stay up to date? To be notified when a Cursor changes, you can use the ContentObserver API. ContentObserver supports a set of callbacks that are invoked when data changes. Cursor has register and unregister methods for ContentObserver objects.

After having seen how the built-in contacts provider works, you may also want to check out the android.provider package in the Javadocs, as it lists more built-in providers. Now that we have covered a bit about using a built-in provider and have the CRUD fundamentals under our belt, we will look at the other side of the coin—creating a ContentProvider.

5.4.2 Creating a ContentProvider

In this section we are going to build a provider that will handle data responsibilities for a generic Widget object we will define. This object is simple, with a name, type, category, and other members, and intentionally generic, so we can focus on the how here and not the why. (The reasons why you might implement a provider in real life are many; for the purposes of this example, our type will be the mythical Widget.)

To create a ContentProvider extend that class and implement the required abstract methods. We will show how this is done specifically in a moment. Before getting to that, it is a good idea to first define a provider constants class that defines the CONTENT_URI and MIME_TYPE your provider will support. In addition, you can place the column names your provider will handle here in one class (or you can use multiple nested inner classes as the built-in contacts system does—we find a flatter approach to be easier to understand).

DEFINING A CONTENT_URI AND MIME_TYPE

In listing 5.18, as a prerequisite to extending the ContentProvider class for a custom provider, we have defined needed constants for our Widget type.

Listing 5.18 WidgetProvider constants, including columns and URI

public final class Widget implements BaseColumns { <1—O Extend BaseColumns public static final String MIME_DIR_PREFIX =

"vnd.android.cursor.dir"; <-1 Define MIME prefix public static final String MIME_ITEM_PREFIX = C for multiple items

"vnd.android.cursor.item"; public static final String MIME_ITEM = "vnd.msi.widget"; public static final String MIME_TYPE_SINGLE =

MIME_ I TEM_ PREFIX + "/" + MIME_ITEM; public static final String MIME_TYPE_MULTIPLE = MIME_DIR_PREFIX + "/" + MIME_ITEM;

public static final String AUTHORITY = Define authority

"com.msi.manning.chapter5.Widget"; ^

public static final String PATH_SINGLE = "widgets/#"

public static final String PATH_MULTIPLE = "widgets"; <-

public static final Uri CONTENT_URI = H

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

public static final String DEFAULT_SORT_ORDER = "updated DESC" ;

Define

MIME

prefix for single item

Define path for single item

Define path for multiple items public static final String NAME = "name"; Define public static final String TYPE = "type"; col u m ns ^ public static final String CATEGORY = "category"; public static final String CREATED = "created"; public static final String UPDATED = "updated";

Define ultimate CONTENT URI

In our Widget-related provider constants class we first extend the BaseColumns class from Android O. This gives our class a few base constants such as _ID. Next we define the MIME_TYPE prefix for a set of multiple items Q and a single item Q. This is outlined in the Android documentation; the convention is that vnd.android.cursor.dir represents multiple items, and vnd.android.cursor.item represents a single item. Thereafter we define a specific MIME item and combine it with the single and multiple paths to create two MIME_TYPE representations Q.

Once we have the MIME details out of the way, we define the authority Q and path for both single Q and multiple H items that will be used in the CONTENT_URI callers we will pass in to use our provider. The multiple-item URI is ultimately the one that callers will start from and the one we publish (they can append specific items from there) ©.

After taking care of all the other details, we define column names that represent the variable types in our Widget object, which are also going to fields in the database table we will use Q. Callers will use these constants to get and set specific fields. That leads us to the next part of the process, extending ContentProvider.

EXTENDING CONTENTPROVIDER

In listing 5.19 we show the beginning of our ContentProvider implementation class, WidgetProvider. In this part of the class we do some housekeeping relating to the database we will use and the URI we are supporting.

Listing 5.19 The first portion of the WidgetProvider ContentProvider public class WidgetProvider extends ContentProvider {

private static final String CLASSNAME =

WidgetProvider.class.getSimpleName(); private static final int WIDGETS = 1; private static final int WIDGET = 2; public static final String DB_NAME = "widgets_db"; public static final String DB_TABLE = "widget"; public static final int DB_VERSION = 1;

Extend

B ContentProvider

C Define database ' constants

<—D Use UriMatcher private SQLiteDatabase db;

private static UriMatcher URI_MATCHER = null;

private static HashMap<String, String> PROJECTION_MAP; <-1

| Use SQLiteDatabase E projection Map static { F reference

WidgetProvider . URI_MATCHER = new UriMatcher (UriMatcher . NO_MATCH); WidgetProvider . URI_MATCHER .addURI(Widget. AUTHORITY,

Widget. PATH_MULTIPLE, WidgetProvider . WIDGETS); WidgetProvider . URI_MATCHER .addURI(Widget. AUTHORITY, Widget. PATH_SINGLE, WidgetProvider .WIDGET);

WidgetProvider. WidgetProvider. WidgetProvider. WidgetProvider. WidgetProvider. WidgetProvider. WidgetProvider.

PROJECTION_MAP = new HashMap<String, String>(); PROJECTION_MAP.put(BaseColumns._ID, "_id" ) ; PROJECTION_MAP.put(Widget.NAME, "name") ; PROJECTION_MAP.put(Widget. TYPE, "type" ) ; PROJECTION_MAP.put(Widget. CATEGORY, "category" ) , PROJECTION_MAP.put(Widget. CREATED, "created" ) ; PROJECTION_MAP.put(Widget. UPDATED, "updated" ) ;

private static class DBOpenHelper extends SQLiteOpenHelper { private static final String DB_CREATE = "CREATE TABLE " + WidgetProvider.DB_TABLE

+ " (_id INTEGER PRIMARY KEY, name TEXT UNIQUE NOT NULL, + "type TEXT, category TEXT, updated INTEGER, created + "INTEGER) ;";

public DBOpenHelper(Context context) {

super(context, WidgetProvider.DB_NAME, null, WidgetProvider. DB_VERSION);

@Override public void onCreate(SQLiteDatabase db) { try {

db . execSQL (DBOpenHelper . DB_CREATE); } catch (SQLException e) { / / log and or handle

Create and open database G

@Override public void onOpen(SQLiteDatabase db) { }

@Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("DROP TABLE IF EXISTS "

+ WidgetProvider .DB_TABLE); this.onCreate(db);

@Override O Override onCreate public boolean onCreate () { <1-'

DBOpenHelper dbHelper = new DBOpenHelper(this.getContext()); this.db = dbHelper.getWritableDatabase() ;

return true;

@Override I Implement public String getType (Uri uri) { <-1 getType method switch (WidgetProvider.URI_MATCHER.match(uri)) { case WIDGETS:

return Widget .MIME_TYPE_MULTIPLE; case WIDGET:

return Widget .MIME_TYPE_SINGLE; default:

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

Our provider extends ContentProvider, which defines the methods we will need to implement O. Then we use several database-related constants to define the database name and table we will use C. After that we include a UriMatcher ©, which we will use when matching types in a moment, and a projection Map for field names ©.

We include a reference to a SQLiteDatabase object; this is what we will use to store and retrieve the data that our provider handles ©. This database is created, opened, and upgraded using a SQLiteOpenHelper in an inner class ©. We have used this helper pattern before, when we worked directly with the database in listing 5.14. The onCreate method of our provider is where the open helper is used to set up the database reference O.

After our setup-related steps we come to the first method a ContentProvider requires us to implement, getType ©. This method is used by the provider to resolve each passed-in Uri to determine if it is supported and if so which type of data the current call is requesting (a single item or the entire set). The MIME_TYPE String we return here is based on the constants we defined in our Widget class.

The next steps we need to cover are the remaining required methods to implement to satisfy the ContentProvider contract. These methods, which are shown in listing 5.20, correspond to the CRUD-related activities used with the contacts provider in the previous section: query, insert, update, and delete.

Listing 5.20 The second portion of the WidgetProvider ContentProvider

@Override public Cursor query(Uri uri, String [] projection,

String selection, String [] selectionArgs, Use query builder O

String sortOrder) { |

SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); <-'

String orderBy = null;

switch (WidgetProvider. URI_MATCHER.match(uri) ) { <-jet Up qUery case WIDGETS: C based on URI

queryBuilder.setTables(WidgetProvider.DB_TABLE); queryBuilder.setProj ectionMap(WidgetProvider.PROJECTION_MAP); break; case WIDGET:

queryBuilder.setTables(WidgetProvider.DB_TABLE); queryBuilder.appendWhere("_id=" + uri.getPathSegments().get(1)); break; default:

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

if (TextUtils.isEmpty(sortOrder)) {

orderBy = Widget. DEFAULT_SORT_ORDER; } else {

orderBy = sortOrder;

Cursor c = queryBuilder.query(this. db, projection, selection, selectionArgs, null, null, Q P^forn query orderBy) ; <-1 to get Cursor c.setNotificationUri(

this . getContext () . getContentResolver () , uri); <1-1 jet notification return c; E Uri on Cursor

@Override public Uri insert(Uri uri, ContentValues initialValues) { long rowId = 0L;

ContentValues values = null;- <-1 Use ContentValues if (initialValues != null) { F in insert method values = new ContentValues(initialValues); } else {

values = new ContentValues();

if (WidgetProvider . URI_MATCHER. match(uri) ! = WidgetProvider.WIDGETS) {

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

Long now = System.currentTimeMillis(); . . . omit defaulting of values for brevity rowId = this.db.insert(WidgetProvider.DB_TABLE, "widget_hack", values) ; <1-1

G Call database insert Get Uri to return O

Uri result = ContentUris . withAppendedId (Widget. CONTENT_URI, rowId); <-

this.getContext().getContentResolver().notifyChange(result, null); <-1

return result; © Notify listeners data was inserted

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

@Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { <-1

Provide update int count = 0; j method switch (WidgetProvider. URI_MATCHER.match(uri)) {

case WIDGETS:

count = this.db.update(WidgetProvider.DB_TABLE, values, selection, selectionArgs); break; case WIDGET:

String segment = uri.getPathSegments().get(1); String where = "";

if (!TextUtils.isEmpty(selection)) { where = " AND (" + selection + ") ";

count = this.db.update(WidgetProvider.DB_TABLE, values,

"_id=" + segment + where, selectionArgs); break; default:

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

this.getContext().getContentResolver().notifyChange(uri, null); return count;

@Override public int delete ( © Provide delete

Uri uri, String selection, String [] selectionArgs) { <1-1 method int count;

switch (WidgetProvider.URI_MATCHER.match(uri)) { case WIDGETS:

count = this.db.delete(WidgetProvider.DB_TABLE, selection, selectionArgs); break; case WIDGET:

String segment = uri.getPathSegments().get(1); String where = "";

if (!TextUtils.isEmpty(selection) ) {

where = " AND (" + selection + ") ";

count = this.db.delete(WidgetProvider.DB_TABLE, "_id=" + segment + where, selectionArgs);

break; default:

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

this.getContext().getContentResolver().notifyChange(uri, null); return count;

In the last part of our WidgetProvider class we show how the ContentProvider methods are implemented. These are the same methods but a different provider that we called earlier in our ProviderExplorer example.

First we use a SQLQueryBuilder inside the query method to append the projection map passed in O and any SQL clauses, along with the correct URI based on our matcher Q, before we make the actual query and get a handle on a Cursor to return ©.

At the end of the query method we use the setNotificationUri method to set the returned Uri to be watched for changes Q. This is an event-based mechanism that can be used to keep track of when Cursor data items are changed, regardless of how changes are made.

Next we see the insert method, where the passed-in ContentValues object is validated and populated with default values if not present Q. After the values are ready, we call the database insert method Q and get the resulting Uri to return with the appended ID of the new record H. After the insert is complete, another notification system is in use, this time for ContentResolver. Here, since we have made a data change, we are informing the ContentResolver what happened so that any registered listeners can be updated ©.

After the insert method is complete, we come to the update Q and delete methods 1). These repeat many of the concepts we have already used. First they match the Uri passed in to a single element or the set, then they call the respective update and delete methods on the database object. Again, at the end of these methods we notify listeners that the data has changed.

Implementing the needed provider methods completes our class. This provider, which now serves the Widget data type, can be used from any application to query, insert, update, or delete data, once we have registered it as a provider with the platform. This is done using the application manifest, which we will look at next. PROVIDER MANIFESTS

In order for the platform to be aware of the content providers that are available and what data types they represent, they must be defined in an application manifest file and installed on the platform. The manifest for our provider is shown in listing 5.21.

Listing 5.21 WidgetProvider AndroidManifest.xml file

<?xml version="1.0" encoding="utf-8 " ?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.msi.manning.chapter5.widget"> <application android:icon="@drawable/icon" android:label="@string/app_short_name"> <activity android:name=".WidgetExplorer" android:label="@string/app_name"> <intent-filter>

<action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>

<provider android:name="WidgetProvider" android:authorities=

"com.msi.manning.chapter5.Widget" /> </application> </manifest>

The significant part of the manifest concerning content provider support is the <provider> element O. This is used to define the class that implements the provider and associate a particular authority with that class.

Additional ContentProvider manifest properties

The properties of a ContentProvider, which are configurable in the manifest, are capable of configuring several important settings beyond the basics, such as specific permissions, initialization order, multiprocess capability, and more. While most ContentProvider implementations won't be required to delve into these details, they are still good to be aware of. For complete and up-to-date ContentProvider properties, see the following Android documentation page: http://code.google.com/an-droid/reference/android/R.styleable.html - AndroidManifestProvider.

A completed project that is capable of inserting, retrieving, updating, and deleting records rounds out our exploration of using and building ContentProvider classes. And with that, we have also now demonstrated many of the ways to store and retrieve data on the Android platform.

0 0

Responses

  • adamo
    How to extend contentprovider class in android?
    4 months ago

Post a comment