Create and use custom Content Provider
In Android, when you have many applications and want to share data between them, you need to implement a content provider as recommended.
This is the syntax:
<standard-prefix>://<authority>/<paths>/<id>
As you have an Android device, you probably have some provided content provider, such as: Contacts, Browser, Settings, CallLog, MediaStore…
If you want to retrieve all contacts:
content://contacts/people
Get a specific contact:
content://contacts/people/3
Get all bookmarks from browser:
content://browser/bookmarks
That’s some common things, you should have known it.
However, you might need to create your own custom content provider to use as well, don’t you?
You might need to refer to this page before reading next part in my tutorials: Android Developers’ Page – Content Provider
Basically, you need to extend to ContentProvider class which provides some abstracts methods:
- getType(): Returns the MIME type of the data at the given URI.
- onCreate(): Called when the provider is being started.
- query(): Receives a request from a client. The result is returned as a Cursor object.
- insert(): Inserts a new record into the content provider.
- delete(): Deletes an existing record from the content provider.
- update(): Updates an existing record from the content provider
For data processing within your content provider, you may choose to store your data in any way you like: database, files, XML or web services. I use SQLite database in this tutorial.
Ok, so I will create a ContentProvider for something called Articles, using a database: “articles.db“, one table “tbl_articles” with three columns: “_id“, “title“, “content” for simplicity.
This is what you have upon starting:
public class ArticleProvider extends ContentProvider { @Override public int delete(Uri uri, String where, String[] whereArgs) { return 0; } @Override public String getType(Uri uri) { return null; } @Override public Uri insert(Uri uri, ContentValues values) { return null; } @Override public boolean onCreate() { return false; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { return null; } @Override public int update(Uri uri, ContentValues values, String where, String[] whereArgs) { return 0; } }
Next is to create a helper class which provides such a way to access SQLite database and manipulate: ArticleDBHelper, will extend SQLiteOpenHelper.
private static class ArticleDBHelper extends SQLiteOpenHelper { public ArticleDBHelper(Context c) { super(c, ArticleMetaData.DATABASE_NAME, null, ArticleMetaData.DATABSE_VERSION); } private static final String SQL_QUERY_CREATE = "CREATE TABLE " + ArticleMetaData.ArticleTable.TABLE_NAME + " (" + ArticleMetaData.ArticleTable.ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + ArticleMetaData.ArticleTable.TITLE + " TEXT NOT NULL, " + ArticleMetaData.ArticleTable.CONTENT + " TEXT NOT NULL" + ");" ; @Override public void onCreate(SQLiteDatabase db) { db.execSQL(SQL_QUERY_CREATE); } private static final String SQL_QUERY_DROP = "DROP TABLE IF EXISTS " + ArticleMetaData.ArticleTable.TABLE_NAME + ";" ; @Override public void onUpgrade(SQLiteDatabase db, int oldVer, int newVer) { db.execSQL(SQL_QUERY_DROP); onCreate(db); } }
We need to provide some constants to use throughout program as well, which is written in a seperate class.
package pete.android.study.provider; import android.net.Uri; import android.provider.BaseColumns; public class ArticleMetaData { private ArticleMetaData() { } public static final String AUTHORITY = "pete.android.study.provider.Articles"; public static final Uri CONTENT_URI = Uri.parse( "content://" + AUTHORITY + "/articles" ); public static final String DATABASE_NAME = "articles.db"; public static final int DATABASE_VERSION = 1; public static final String CONTENT_TYPE_ARTICLES_LIST = "vnd.android.cursor.dir/vnd.pete.articles"; public static final String CONTENT_TYPE_ARTICLE_ONE = "vnd.android.cursor.item/vnd.pete.articles"; public class ArticleTable implements BaseColumns { private ArticleTable() { } public static final String TABLE_NAME = "tbl_articles"; public static final String ID = "_id"; public static final String TITLE = "title"; public static final String CONTENT = "content"; } }
ArticleMetaData class defines some required constant values to use:
+ AUTHORITY: this is the name of your content provider
+ CONTENT_URI: is your content provider URI for other applications to access data from it.
Let’s say if you want to get all articles, it would be like:
content://pete.android.study.provider.Articles/articles
To retrieve a specific article:
content://pete.android.study.provider.Articles/articles/13
+ CONTENT_TYPE_ARTICLES_LIST, CONTENT_TYPE_ARTICLE_ONE: simply define what kind of MIME value return.
Syntax:
for a list: vnd.android.cursor.dir/vnd.your-domain.your-item-name
for one item: vnd.android.cursor.item/vnd.your-domain.your-your-item-name
The object of UriMatcher will parse the input content URI to define which kind of MIME used to process. So when you need to return both list and single item, you need to define those two of CONTENT_TYPE.
Here the fully implementation:
package pete.android.study.provider; import java.util.HashMap; import android.content.ContentProvider; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.UriMatcher; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteQueryBuilder; import android.net.Uri; import android.text.TextUtils; public class ArticleProvider extends ContentProvider { private static final UriMatcher sUriMatcher; private static final int ARTICLE_TYPE_LIST = 1; private static final int ARTICLE_TYPE_ONE = 2; static { sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); sUriMatcher.addURI(ArticleMetaData.AUTHORITY, "articles", ARTICLE_TYPE_LIST); sUriMatcher.addURI(ArticleMetaData.AUTHORITY, "articles/#", ARTICLE_TYPE_ONE); } private static final HashMap<String, String> sArticlesProjectionMap; static { sArticlesProjectionMap = new HashMap<String, String>(); sArticlesProjectionMap.put(ArticleMetaData.ArticleTable.ID, ArticleMetaData.ArticleTable.ID); sArticlesProjectionMap.put(ArticleMetaData.ArticleTable.TITLE, ArticleMetaData.ArticleTable.TITLE); sArticlesProjectionMap.put(ArticleMetaData.ArticleTable.CONTENT, ArticleMetaData.ArticleTable.CONTENT); } private static class ArticleDBHelper extends SQLiteOpenHelper { public ArticleDBHelper(Context c) { super(c, ArticleMetaData.DATABASE_NAME, null, ArticleMetaData.DATABSE_VERSION); } private static final String SQL_QUERY_CREATE = "CREATE TABLE " + ArticleMetaData.ArticleTable.TABLE_NAME + " (" + ArticleMetaData.ArticleTable.ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + ArticleMetaData.ArticleTable.TITLE + " TEXT NOT NULL, " + ArticleMetaData.ArticleTable.CONTENT + " TEXT NOT NULL" + ");" ; @Override public void onCreate(SQLiteDatabase db) { db.execSQL(SQL_QUERY_CREATE); } private static final String SQL_QUERY_DROP = "DROP TABLE IF EXISTS " + ArticleMetaData.ArticleTable.TABLE_NAME + ";" ; @Override public void onUpgrade(SQLiteDatabase db, int oldVer, int newVer) { db.execSQL(SQL_QUERY_DROP); onCreate(db); } } private ArticleDBHelper mDbHelper; @Override public boolean onCreate() { mDbHelper = new ArticleDBHelper(getContext()); return false; } @Override public int delete(Uri uri, String where, String[] whereArgs) { SQLiteDatabase db = mDbHelper.getWritableDatabase(); int count = 0; switch(sUriMatcher.match(uri)) { case ARTICLE_TYPE_LIST: count = db.delete(ArticleMetaData.ArticleTable.TABLE_NAME, where, whereArgs); break; case ARTICLE_TYPE_ONE: String rowId = uri.getPathSegments().get(1); count = db.delete(ArticleMetaData.ArticleTable.TABLE_NAME, ArticleMetaData.ArticleTable.ID + " = " + rowId + (!TextUtils.isEmpty(where) ? " AND (" + where + ")" : ""), whereArgs); break; default: throw new IllegalArgumentException("Unknown URI: " + uri); } getContext().getContentResolver().notifyChange(uri, null); return count; } @Override public String getType(Uri uri) { switch(sUriMatcher.match(uri)) { case ARTICLE_TYPE_LIST: return ArticleMetaData.CONTENT_TYPE_ARTICLES_LIST; case ARTICLE_TYPE_ONE: return ArticleMetaData.CONTENT_TYPE_ARTICLE_ONE; default: throw new IllegalArgumentException("Unknown URI: " + uri); } } @Override public Uri insert(Uri uri, ContentValues values) { if(sUriMatcher.match(uri) != ARTICLE_TYPE_LIST) { throw new IllegalArgumentException("[Insert](01)Unknown URI: " + uri); } SQLiteDatabase db = mDbHelper.getWritableDatabase(); long rowId = db.insert(ArticleMetaData.ArticleTable.TABLE_NAME, null, values); if(rowId > 0) { Uri articleUri = ContentUris.withAppendedId(ArticleMetaData.CONTENT_URI, rowId); getContext().getContentResolver().notifyChange(articleUri, null); return articleUri; } throw new IllegalArgumentException("[Insert](02)Unknown URI: " + uri); } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { SQLiteQueryBuilder builder = new SQLiteQueryBuilder(); switch(sUriMatcher.match(uri)) { case ARTICLE_TYPE_LIST: builder.setTables(ArticleMetaData.ArticleTable.TABLE_NAME); builder.setProjectionMap(sArticlesProjectionMap); break; case ARTICLE_TYPE_ONE: builder.setTables(ArticleMetaData.ArticleTable.TABLE_NAME); builder.setProjectionMap(sArticlesProjectionMap); builder.appendWhere(ArticleMetaData.ArticleTable.ID + " = " + uri.getPathSegments().get(1)); break; default: throw new IllegalArgumentException("Unknown URI: " + uri); } SQLiteDatabase db = mDbHelper.getReadableDatabase(); Cursor queryCursor = builder.query(db, projection, selection, selectionArgs, null, null, null); queryCursor.setNotificationUri(getContext().getContentResolver(), uri); return queryCursor; } @Override public int update(Uri uri, ContentValues values, String where, String[] whereArgs) { SQLiteDatabase db = mDbHelper.getWritableDatabase(); int count = 0; switch(sUriMatcher.match(uri)) { case ARTICLE_TYPE_LIST: count = db.update(ArticleMetaData.ArticleTable.TABLE_NAME, values, where, whereArgs); break; case ARTICLE_TYPE_ONE: String rowId = uri.getPathSegments().get(1); count = db.update(ArticleMetaData.ArticleTable.TABLE_NAME, values, ArticleMetaData.ArticleTable.ID + " = " + rowId + (!TextUtils.isEmpty(where) ? " AND (" + ")" : ""), whereArgs); default: throw new IllegalArgumentException("Unknown URI: " + uri); } getContext().getContentResolver().notifyChange(uri, null); return count; } }
When you insert/update/delete, always remember to call “notifyChange()” to the URI that has been used.
When you query values, always remember to to call “setNotificationUri()” for Cursor.
That’s done for creating your custom content provider. In order to use, you need to register it to AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="pete.android.study" android:versionCode="1" android:versionName="1.0"> <uses-sdk android:minSdkVersion="10" /> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".MainActivity" 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="pete.android.study.provider.ArticleProvider" android:authorities="pete.android.study.provider.Articles"> </provider> </application> </manifest>
android:name = the class name that handling content provider
android:authorities = the authority name you define
You’ve done creating your own custom Content Provider 🙂
Get my sample project for custom Content Provider: Sample Project
Cheers,
Pete Houston
hey thanx for the nice tutorial, but am still wondering if this can still be done with a web service or mysql instead of using android’s sqlite…
Nice tutorila
if anyone have anytype of resource or link for the content provider using xml file,text file or web services??
please share with me
Thank You…….
replaceitwith(android:name=”pete.android.study.provider.ArticleProvider”,android:name=”ArticleProvider”); in manifest
super(c, ArticleMetaData.DATABASE_NAME, null, ArticleMetaData.DATABSE_VERSION); correct DATABASE spelling
awesome tutorial….. 🙂 thumbs up
how can i download your sample projects?
A perfect one for understanding
sUriMatcher.addURI(ArticleMetaData.AUTHORITY, “articles”, ARTICLE_TYPE_LIST);
sUriMatcher.addURI(ArticleMetaData.AUTHORITY, “articles/#”, ARTICLE_TYPE_ONE);
pls explain the meaning of these two lines,nd also the difference between these 2 lines
The first one is used for querying all “articles”; the second one is to query a single “article” specified by _id.
How can I use this ContentProvider in other application.
Awesum!!…………………supr gr8 tutorial…………….5 stars…keep postin… 🙂
Cuối cùng thì cũng fix xong anh nhỉ :))