Home > Tutorials > Create and use custom Content Provider

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

 

Advertisements
  1. Brian
    February 18, 2014 at 8:15 pm

    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…

  2. Ram
    March 22, 2013 at 5:01 pm

    Nice tutorila

  3. March 18, 2013 at 4:35 pm

    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…….

  4. March 17, 2013 at 7:37 pm

    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

  5. newbie
    March 12, 2013 at 3:51 am

    how can i download your sample projects?

  6. r
    March 5, 2012 at 7:31 am

    A perfect one for understanding

  7. shephali
    February 13, 2012 at 5:01 pm

    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

    • February 24, 2012 at 10:25 am

      The first one is used for querying all “articles”; the second one is to query a single “article” specified by _id.

  8. ab1209
    January 30, 2012 at 12:22 pm

    How can I use this ContentProvider in other application.

  9. January 18, 2012 at 11:35 pm

    Awesum!!…………………supr gr8 tutorial…………….5 stars…keep postin… 🙂

  10. trungh0
    June 20, 2011 at 1:54 am

    Cuối cùng thì cũng fix xong anh nhỉ :))

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: