Android Development Part 3: Content Provider
This tutorial assumes that you know how to make basic Activity and know how SQL works. On this tutorial, we are going to look at Content Providers. Content providers manage access to a structured set of data. They encapsulate the data, and provide mechanisms for defining data security. In other words, Content Provider is Android's way of storing data. Android can store data in many ways:
- Files
- SQLite database
- On the web
- Any other persistent storage location your application can access
Each content provider exposes a public URI (wrapped as a Uri object) that uniquely identifies its data set. All URIs for providers begin with the string "content://". Android defines CONTENT_URI constants for all the providers that come with the platform. If we store data on the web, it can be "http://" or "ftp://", depending on the protocol of course. For now, we will work with Android's internal db - SQLite.
To create a content provider, we must create a class that extends ContentProvider. On the top of that, we must override a few methods of the ContentProvider class.
public class etrter extends ContentProvider {
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// TODO Auto-generated method stub
return 0;
}
@Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean onCreate() {
// TODO Auto-generated method stub
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// TODO Auto-generated method stub
return null;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// TODO Auto-generated method stub
return 0;
}
}
After that, we must add a couple of member static variables:
public static final String PROVIDER_NAME = "cs.elc.w12.provider.CoolProverbs";
public static final Uri CONTENT_URI = Uri.parse("content://" + PROVIDER_NAME + "/proverb");
public static final String _ID = "_id";
public static final String CONTENT = "proverb";
public static final String ORIGIN = "proverb_origin";
public static final String TIME = "time";
private static final int PROVERBS = 1;
private static final int PROVERBS_ID = 2;
private static final UriMatcher uriMatcher;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(PROVIDER_NAME, "proverb", PROVERBS);
uriMatcher.addURI(PROVIDER_NAME, "proverb/#", PROVERBS_ID);
}
private SQLiteDatabase proverbsDB;
private static final String DATABASE_NAME = "Proverbs";
private static final String DATABASE_TABLE = "proverbsList";
private static final int DATABASE_VERSION = 1;
private static final String DATABASE_CREATE = "CREATE TABLE " + DATABASE_TABLE + " (" + _ID + " INTEGER PRIMARY KEY, " +
"proverb TEXT NOT NULL, proverb_origin TEXT NULL, time TEXT NULL);";
- PROVIDER_NAME is the name of our provider. We also use this for the URI and many various things.
- CONTENT_URI is the full URL to our content provider.
- _ID, CONTENT, ORIGIN and TIME are column names of the table.
- PROVERBS and PROVERBS_ID are unique integers for this content provider.
- proverbsDB is a SQLiteDatabase variable type. This is what we mainly use to deal with the SQLite database.
- DATABASE_NAME, DATABASE_TABLE, DATABASE_VERSION, DATABASE_CREATE are all database related stuff.
We also need to create a DatabaseHelper that will help us deal with SQLite. Create an inner class called DatabaseHelper which extends SQLiteOpenHelper class.
private static class DatabaseHelper extends SQLiteOpenHelper {
DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(DATABASE_CREATE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS " + DATABASE_TABLE);
onCreate(db);
}
}
First, we implement the DatabaseHelper constructor, we must also call the parent constructor and pass the context. We override two methods: onCreate() and onUpgrade(). I wouldn't tell much details about this, but let's assume that this helper class will create the database for us if it doesn't exist.
Now, let's implement the delete() method.
public int delete(Uri arg0/* uri */, String arg1/* selection */, String[] arg2/* delectionArgs */) {
int count = 0;
switch(uriMatcher.match(arg0)) {
case PROVERBS:
count = proverbsDB.delete(DATABASE_TABLE, arg1, arg2);
break;
case PROVERBS_ID:
String id = arg0.getPathSegments().get(1);
count = proverbsDB.delete(DATABASE_TABLE, _ID + " = " + id +
(!TextUtils.isEmpty(arg1) ? " AND (" + arg1 + ")" : ""), arg2);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + arg0);
}
getContext().getContentResolver().notifyChange(arg0, null);
return count;
}
The first argument of this method is the uri, second is the selection, third are the things to be deleted. We use switch to see if the first argument being pass is a PROVERBS (all data in table) or PROVERBS_ID (a specific data in the table, ID of a data). As you can see, we use proverbsDB's delete() method to delete data from SQLite. The getContext().getContentResolver().notifyChange() notifies the changes. We can catch this from the Activity side, but to keep things simple, let's leave this alone.
Now, let's implement onCreate()
public boolean onCreate() {
Context context = getContext();
DatabaseHelper dbHelper = new DatabaseHelper(context);
proverbsDB = dbHelper.getWritableDatabase();
return (proverbsDB == null) ? false : true;
}
getContext() returns whatever this is currently running in. We need to pass this context to the database helper that we created. Now, getWritableDatabase() grabs the database. It should create it if it doesn't exists. In either case, the onCreate() method will return true or false, depending on the status.
Now, let's implement getType()
public String getType(Uri uri) {
switch(uriMatcher.match(uri)) {
case PROVERBS:
return "vnd.android.cursor.dir/vnd.ecl.proverb";
case PROVERBS_ID:
return "vnd.android.cursor.item/vnd.ecl.proverb";
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
}
getType() method handles the MIME type of the data at the given URI. On this case, we return "vnd.android.cursor.dir/vnd.ecl.proverb" for PROVERBS for multiple items and we return "vnd.android.cursor.item/vnd.ecl.proverb" for PROVERBS_ID for a single item. As you can see, the one with "dir" are for multiple items and "item" for single item. See this to learn more about it.
Now, let's implement insert()
public Uri insert(Uri uri, ContentValues values) {
long rowID = proverbsDB.insert(DATABASE_TABLE, "", values);
// if added successfully
if (rowID > 0) {
Uri _uri = ContentUris.withAppendedId(CONTENT_URI, rowID);
getContext().getContentResolver().notifyChange(_uri, null);
return _uri;
}
throw new SQLException("Failed to insert row into " + uri);
}
The same way we handle delete, we also use proverbsDB's method - on this case, insert(). We notify for changes and we return the uri of the inserted data. Throw an SQLException otherwise.
Now, let's implement query
public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3,
String arg4) {
SQLiteQueryBuilder sqlBuilder = new SQLiteQueryBuilder();
sqlBuilder.setTables(DATABASE_TABLE);
if(uriMatcher.match(arg0) == PROVERBS_ID) {
sqlBuilder.appendWhere(_ID + " = " + arg0.getPathSegments().get(1));
}
if(arg4 == null || arg4 == "") {
arg4 = CONTENT;
}
Cursor cursor = sqlBuilder.query(proverbsDB, arg1, arg2, arg3, null, null, arg4);
cursor.setNotificationUri(getContext().getContentResolver(), arg0);
return cursor;
}
The query() method returns a Cursor object. This cursor object is what we'll use to move around the returned result after querying something. We use SQLiteQueryBuilder to run a query. SQLiteQueryBuilder's query() method returns a Cursor object, which we then return.
Finally, let's implement update() method
public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {
int count = 0;
switch (uriMatcher.match(arg0)) {
case PROVERBS:
count = proverbsDB.update(DATABASE_TABLE, arg1, arg2, arg3);
break;
case PROVERBS_ID:
String id = arg0.getPathSegments().get(1);
count = proverbsDB.update(DATABASE_TABLE, arg1, _ID + " = " + id +
(!TextUtils.isEmpty(arg2) ? " AND (" + arg1 + ")" : ""), arg3);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + arg0);
}
getContext().getContentResolver().notifyChange(arg0, null);
return count;
}
Very similar way as insert() and delete() methods, the only difference is that we have an argument of what needed to be updated.
Now that we have completed the ContentProvider object, the Activity side should be pretty straight forward. To insert data, we must create ContentValues variable which will hold the data to be inserted.
ContentValues values = new ContentValues();
values.put(MyProverbsProvider._ID, "1");
values.put(MyProverbsProvider.CONTENT, "Give a man a fish and you feed him for a day; teach a man to fish and you feed him for a lifetime");
values.put(MyProverbsProvider.ORIGIN, "Asian - Chinese");
values.put(MyProverbsProvider.TIME, "Unkown");
We then use the insert() method from getContentResolver() to insert it
getContentResolver().insert(MyProverbsProvider.CONTENT_URI, values);
getContentResolver() returns whatever content provider it is available in a package. On this case, it should return the ContentProvider that we have created.
To grab data, we do the following
Cursor cursor = managedQuery(MyProverbsProvider.CONTENT_URI, null, null, null, "_id");
if (cursor.moveToFirst()) {
do {
Toast.makeText(this,
"Content: " + cursor.getString(cursor.getColumnIndex(MyProverbsProvider.CONTENT)) + "\n" +
"Origin: " + cursor.getString(cursor.getColumnIndex(MyProverbsProvider.ORIGIN)) + "\n" +
"Time: " + cursor.getString(cursor.getColumnIndex(MyProverbsProvider.TIME)) + "\n" +
"ID: " + cursor.getString(cursor.getColumnIndex(MyProverbsProvider._ID)) + "\n"
, Toast.LENGTH_LONG).show();
} while (cursor.moveToNext());
}
managedQuery() returns the cursor from query() of the content provider we created. We then use cursor's moveToFirst() and moveToNext() methods to traverse on each data in the table.
That's all for Content Provider, hope you enjoy! I'll post the source codes soon, so stay tune! For now, feel free to explore the web for more tutorials and information.