Any Uri
in Android that begins with the content://
scheme represents a resource served up by a content provider. Content providers offer data encapsulation using Uri
instances as handles. You neither know nor care where the data represented by the Uri
comes from, as long as it is available to you when needed. The data could be stored in a SQLite database, in flat files, or on some far-off server accessed over the Internet.
Given a Uri
, you can perform basic CRUD (create, read, update, delete) operations using a content provider. Uri
instances can represent either collections or individual pieces of content. Given a collection Uri
, you can create new pieces of content via insert operations. Given an instance Uri
, you can read data represented by the Uri
, update that data, or delete the instance outright.
Android lets you use existing content providers or create your own. This chapter covers using content providers. Chapter 27 explains how you can serve up your own data using the content provider framework.
The simplified model of the construction of a content Uri
is the scheme, the namespace of data, and, optionally, the instance identifier—all separated by slashes in URL-style notation. The scheme of a content Uri
is always content://
.
So, a content Uri
of content://constants/5
represents the constants
instance with an identifier of 5
.
The combination of the scheme and the namespace is known as the base Uri
of a content provider, or a set of data supported by a content provider. In the preceding example, content://constants
is the base Uri
for a content provider that serves up information about “constants” (in this case, physical constants).
The base Uri
can be more complicated. For example, if the base Uri
for contacts were content://contacts/people
, the Contacts content provider may serve up other data using other base Uri
values.
The base Uri
represents a collection of instances. The base Uri
combined with an instance identifier (e.g., 5
) represents a single instance.
Most of the Android APIs expect these to be Uri
objects, though in common discussion, it is simpler to think of them as strings. The Uri.parse()
static method creates a Uri
from the string representation.
So, where do these Uri
instances come from?
The most popular starting point, if you know the type of data you want to work with, is to get the base Uri
from the content provider itself in code. For example, CONTENT_URI
is the base Uri
for contacts represented as people; this maps to content://contacts/people
. If you just need the collection, this Uri
works as is. If you need an instance and know its identifier, you can call addId()
on the Uri
to inject it, so you have a Uri
for the instance.
You might also get Uri
instances handed to you from other sources, such as getting Uri
handles for contacts via subactivities responding to ACTION_PICK
intents. In this case, the Uri
is truly an opaque handle, unless you decide to pick it apart using the various getters on the Uri
class.
You can also hardwire literal String
objects (e.g., “content://contacts/people”
) and convert them into Uri
instances via Uri.parse()
. This is not an ideal solution, as the base Uri
values could conceivably change over time. For example, the Contacts content provider's base Uri
is no longer content://contacts/people
due to an overhaul of that subsystem.
Given a base Uri
, you can run a query to return data from the content provider related to that Uri
. This has much of the feel of SQL—you specify the “columns” to return, the constraints to determine which “rows” to return, a sort order, and so on. The difference is that this request is being made of a content provider, not directly of some database (e.g., SQLite).
The nexus of this is the managedQuery()
method available to your activity. This method takes five parameters:
Uri
of the content provider to query, or the instance Uri
of a specific object to queryWHERE
clause?
characters that appear thereORDER BY
clauseThis method returns a Cursor
object, which you can use to retrieve the data returned by the query.
Properties are to content providers as columns are to databases. In other words, each instance (row) returned by a query consists of a set of properties (columns), each representing some piece of data.
This should make more sense given an example.
Our content provider examples come from the ContentProvider/ConstantsPlus
sample application, specifically the ConstantsBrowser
class:
constantsCursor=managedQuery(Provider.Constants.CONTENT_URI,
PROJECTION, null, null, null);
In the call to managedQuery()
, we provide the following:
Uri
passed into the activity by the caller (CONTENT_URI
); in this case, representing the collection of physical constants managed by the content providernull
values, indicating that we do not need a constraint clause (the Uri
represents the instance we need), nor parameters for the constraint, nor a sort order (we should get only one entry back)private static final String[] PROJECTION = new String[] {
Provider.Constants._ID, Provider.Constants.TITLE,
Provider.Constants.VALUE};
The biggest “magic” here is the list of properties. The lineup of what properties are possible for a given content provider should be provided by the documentation (or source code) for the content provider itself. In this case, we define logical values on the Provider
content provider implementation class that represent the various properties (namely, the unique identifier, the display name or title, and the value of the constant).
Now that we have a Cursor
via managedQuery()
, we have access to the query results and can do whatever we want with them. We might, for example, manually extract data from the Cursor
to populate widgets or other objects.
However, if the goal of your query is to return a list from which the user should choose an item, you probably should consider using SimpleCursorAdapter
. This class bridges between the Cursor
and a selection widget, such as a ListView
or Spinner
. Pour the Cursor
into a SimpleCursorAdapter
, hand the adapter off to the widget, and you're set—your widget will show the available options.
For example, here is the onCreate()
method from ConstantsBrowser
, which gives the user a list of physical constants:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
constantsCursor=managedQuery(Provider.Constants.CONTENT_URI,
PROJECTION, null, null, null);
ListAdapter adapter=new SimpleCursorAdapter(this,
R.layout.row, constantsCursor,
new String[] {Provider.Constants.TITLE,
Provider.Constants.VALUE},
new int[] {R.id.title, R.id.value});
setListAdapter(adapter);
registerForContextMenu(getListView());
}
After executing the managedQuery()
and getting the Cursor
, ConstantsBrowser
creates a SimpleCursorAdapter
with the following parameters:
Context
) creating the adapter; in this case, the ConstantsBrowser
itselfR.layout.row
)constantsCursor
)View
instances (TITLE
and VALUE
)TextView
widgets in the list entry layout that those properties should go into (R.id.title
and R.id.value
)After that, we put the adapter into the ListView
, and we get the result shown in Figure 26–1.
If you need more control over the views than you can reasonably achieve with the stock view construction logic, subclass SimpleCursorAdapter
and override getView()
to create your own widgets to go into the list, as demonstrated earlier in this book.
And, of course, you can manually manipulate the Cursor
(e.g., moveToFirst()
, getString()
), as demonstrated in Chapter 22.
Figure 26–1. ConstantsBrowser, showing a list of physical constants
Of course, content providers would be astonishingly weak if you couldn't add or remove data from them, as well as update what is there. Fortunately, content providers offer these abilities.
To insert data into a content provider, you have two options available on the ContentProvider
interface (available through getContentProvider()
to your activity):
insert()
with a collection Uri
and a ContentValues
structure describing the initial set of data to put in the row.bulkInsert()
with a collection Uri
and an array of ContentValues
structures to populate several rows at once.The insert()
method returns a Uri
for you to use for future operations on that new object. The bulkInsert()
method returns the number of created rows; you would need to do a query to retrieve the data you just inserted.
For example, here is a snippet of code from ConstantsBrowser
to insert a new constant into the content provider, given a DialogWrapper
that can provide access to the title and value of the constant:
private void processAdd(DialogWrapper wrapper) {
ContentValues values=new ContentValues(2);
values.put(Provider.Constants.TITLE, wrapper.getTitle());
values.put(Provider.Constants.VALUE, wrapper.getValue());
getContentResolver().insert(Provider.Constants.CONTENT_URI,
values);
constantsCursor.requery();
}
Since we already have an outstanding Cursor
for the content provider's contents, we call requery()
on that to update the Cursor
's contents. This, in turn, will update any SimpleCursorAdapter
you may have wrapping the Cursor
, and that will update any selection widgets (e.g., ListView
) you have using the adapter.
To delete one or more rows from the content provider, use the delete()
method on ContentResolver
. This works akin to a SQL DELETE
statement and takes three parameters:
Uri
representing the collection (or instance) you wish to updateWHERE
clause, to determine which rows should be updated?
characters that appear thereBinary large objects (BLOBs) are supported in many databases, including SQLite. However, the Android model is more aimed at supporting such hunks of data via their own separate content Uri
values. A content provider, therefore, does not provide direct access to binary data, like photos, via a Cursor
. Rather, a property in the content provider will give you the content Uri
for that particular BLOB. You can use getInputStream()
and getOutputStream()
on your ContentProvider
to read and write the binary data.
Quite possibly, the rationale is to minimize unnecessary data copying. For example, the primary use of a photo in Android is to display it to the user. The ImageView
widget can do just that, via a content Uri
to a JPEG file. By storing the photo in a manner that has its own Uri
, you do not need to copy data out of the content provider into some temporary holding area just to be able to display it—just use the Uri
. The expectation, presumably, is that few Android applications will do much more than upload binary data and use widgets or built-in activities to display that data.