Writing your own ContentProvider
Hey everyone,
So the following tutorial will hopefully give you a good idea of how to implement your own ContentProvider. I know that there are a lot of pretty good sites out there with some good code snippets, but I’ve noticed that not many really help the developer understand what’s going on step by step so my goal is to not only provide an example of a fully implemented ContentProvider but also to step you through the process.
So I’ll start by just posting all of the code, and I’ll go through it slowly afterwards. We’ll start with the Custom Content Provider itself:
package jason.wei.apps.NotePad.providers; 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.SQLException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteQueryBuilder; import android.net.Uri; import android.util.Log; /** * @author Jason Wei * */ public class NotesContentProvider extends ContentProvider { private static final String TAG = "NotesContentProvider"; private static final String DATABASE_NAME = "notes.db"; private static final int DATABASE_VERSION = 1; private static final String NOTES_TABLE_NAME = "notes"; public static final String AUTHORITY = "jason.wei.apps.notes.providers.NotesContentProvider"; private static final UriMatcher sUriMatcher; private static final int NOTES = 1; private static final int NOTES_ID = 2; private static HashMap<String, String> notesProjectionMap; private static class DatabaseHelper extends SQLiteOpenHelper { DatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE " + NOTES_TABLE_NAME + " (" + Notes.NOTE_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + Notes.TITLE + " VARCHAR(255)," + Notes.TEXT + " LONGTEXT" + ");"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Log.w(TAG, "Upgrading database from version " + oldVersion + " to " + newVersion + ", which will destroy all old data"); db.execSQL("DROP TABLE IF EXISTS " + NOTES_TABLE_NAME); onCreate(db); } } private DatabaseHelper dbHelper; @Override public int delete(Uri uri, String where, String[] whereArgs) { SQLiteDatabase db = dbHelper.getWritableDatabase(); switch (sUriMatcher.match(uri)) { case NOTES: break; case NOTES_ID: where = where + "_id = " uri.getLastPathSegment(); break; default: throw new IllegalArgumentException("Unknown URI " + uri); } int count = db.delete(NOTES_TABLE_NAME, where, whereArgs); getContext().getContentResolver().notifyChange(uri, null); return count; } @Override public String getType(Uri uri) { switch (sUriMatcher.match(uri)) { case NOTES: return Notes.CONTENT_TYPE; default: throw new IllegalArgumentException("Unknown URI " + uri); } } @Override public Uri insert(Uri uri, ContentValues initialValues) { if (sUriMatcher.match(uri) != NOTES) { throw new IllegalArgumentException("Unknown URI " + uri); } ContentValues values; if (initialValues != null) { values = new ContentValues(initialValues); } else { values = new ContentValues(); } SQLiteDatabase db = dbHelper.getWritableDatabase(); long rowId = db.insert(NOTES_TABLE_NAME, Notes.TEXT, values); if (rowId > 0) { Uri noteUri = ContentUris.withAppendedId(Notes.CONTENT_URI, rowId); getContext().getContentResolver().notifyChange(noteUri, null); return noteUri; } throw new SQLException("Failed to insert row into " + uri); } @Override public boolean onCreate() { dbHelper = new DatabaseHelper(getContext()); return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); qb.setTables(NOTES_TABLE_NAME); qb.setProjectionMap(notesProjectionMap); switch (sUriMatcher.match(uri)) { case NOTES: break; case NOTES_ID: selection = selection + "_id = " uri.getLastPathSegment(); break; default: throw new IllegalArgumentException("Unknown URI " + uri); } SQLiteDatabase db = dbHelper.getReadableDatabase(); Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder); c.setNotificationUri(getContext().getContentResolver(), uri); return c; } @Override public int update(Uri uri, ContentValues values, String where, String[] whereArgs) { SQLiteDatabase db = dbHelper.getWritableDatabase(); int count; switch (sUriMatcher.match(uri)) { case NOTES: count = db.update(NOTES_TABLE_NAME, values, where, whereArgs); break; default: throw new IllegalArgumentException("Unknown URI " + uri); } getContext().getContentResolver().notifyChange(uri, null); return count; } static { sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); sUriMatcher.addURI(AUTHORITY, NOTES_TABLE_NAME, NOTES); sUriMatcher.addURI(AUTHORITY, NOTES_TABLE_NAME + "/#", NOTES_ID); notesProjectionMap = new HashMap<String, String>(); notesProjectionMap.put(Notes.NOTE_ID, Notes.NOTE_ID); notesProjectionMap.put(Notes.TITLE, Notes.TITLE); notesProjectionMap.put(Notes.TEXT, Notes.TEXT); } }
And next a little helper class that keeps all of the columns organized and readily accessible:
public class Note { public Note() { } public static final class Notes implements BaseColumns { private Notes() { } public static final Uri CONTENT_URI = Uri.parse("content://" + NotesContentProvider.AUTHORITY + "/notes"); public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.jwei512.notes"; public static final String NOTE_ID = "_id"; public static final String TITLE = "title"; public static final String TEXT = "text"; } }
(Note that this is similar to how Google provides you with the columns, i.e. People._ID, People.NUMBER, CallLog.CACHED_NAME, etc)
Okay so let’s go through this step by step. First, when you extend ContentResolver, there are 6 methods you need to overwrite:
query()
insert()
update()
delete()
getType()
onCreate()
Normally these are just wrapper functions around the raw SQL queries. For instance, the method:
db.delete(tableName, where, whereArgs);
Is simply just a wrapper around the SQL query that looks something like:
"delete from " + tableName + " where " + where + " ? " + whereArgs"
So for those who know SQL, if I wanted to delete the note with title “Hello World” then my queries would look like:
// wrapper query db.delete(NOTES_TABLE_NAME, Notes.TITLE + "= ' " + "Hello World" + " ' ", null); // real query when translated String query = "delete from notes where title = 'Hello World' ";
And so as you look at how I overwrite the 6 methods, you’ll see that I’m simply taking in the parameters and inputting them appropriately into the wrapper methods. Of course, you can override them as you like depending on what you want your application to do. Perhaps your application is ONLY worried about retrieving the average of some numbers. Then in your query() method, maybe instead of querying for the numbers normally, and then always having to iterate through the numbers and find the average from the JAVA side, you could customize your query to automatically return the average BY DEFAULT.
But in case you’re still confused, let’s look at an example:
public int delete(Uri uri, String where, String[] whereArgs) { SQLiteDatabase db = dbHelper.getWritableDatabase(); switch (sUriMatcher.match(uri)) { case NOTES: break; case NOTES_ID: where = where + "_id = " uri.getLastPathSegment(); break; default: throw new IllegalArgumentException("Unknown URI " + uri); } int count = db.delete(NOTES_TABLE_NAME, where, whereArgs); getContext().getContentResolver().notifyChange(uri, null); return count; }
So basically you pass in the Uri (in our case it will be Notes.CONTENT_URI to tell the system which table you’re going to target) and if the Uri matches NOTES then we use our SQLiteDatabase (retrieved in the first line) to delete from the notes table using the where arguments passed in. Another option we have is to match against the Uri NOTES_ID, which will allow us to easily embed a specific note ID to delete. Practically, the difference is that the former Uri allows you all the flexibility you want as you can make your where string match any range of notes to be deleted. The latter is more for identifying specific notes and allows the client (i.e. the developer using the ContentProvider) to not have to worry about even passing in a where argument.
A similar idea goes into overriding query(), insert(), and update(), and in our onCreate() method we are simply using our SQLiteDatabase to execute a CREATE TABLE statement where we build our table using our defined columns (i.e. Notes.NOTE_ID, Notes.TITLE, and Notes.TEXT) and also make sure we use the proper SQLite3 column types.
One thing to note here is that Android requires that the unique id column have name “_id”.
This is extremely important as otherwise your content provider will not get registered properly.
Now, one last thing before I get to how you register it in your Manifest is what this AUTHORITY String is all about. In my case it was:
public static final String AUTHORITY = "jason.wei.apps.notes.providers.NotesContentProvider";
So the authority you can more or less define in anyway that you like, but typically it will look something like:
{package name}.providers.{Custom Provider name}
For organizational purposes, this means that in your Android project, you will have to create a new package called:
{package name}.providers
In order for the Uri path to be correct (again pretty obvious, but who knows).
And so this AUTHORITY basically just helps you define the CONTENT_URI of your ContentProvider (i.e. the path to your database), and you will register your provider in your Manifest like so:
<provider android:name="jason.wei.apps.notes.providers.NotesContentProvider" android:authorities="jason.wei.apps.notes.providers.NotesContentProvider" />
And so you see that I define my content providers in a way such that the project path is equal to the authorities path (again, not required, but probably recommended for simplicity and organizational issues).
And that’s it! Now you have a fully registered and created ContentProvider. My next example/tutorial will show you how to write cool wrapper functions that help you make queries and manipulate data in whatever way you want!
Let me know if you have questions, otherwise Happy Coding.
– jwei
Very helpfull, clear, it’s 5 th tuime i read tutos on this, but this time i really understand
Hi,
Thanks for tutorial. Its very helpful. But I have a question,
In line 106: long rowId = db.insert(NOTES_TABLE_NAME, Notes.TEXT, values);
you have Notes.TEXT, what about the TITLE? I am new to this, I have a table with four columns, I want to know why do we have only the text value being entered? I understand that in the Notes example, there is only a title and a text for each Note, is it that the title gets appended to the uri? I am a bit lost here. How do I deal with a db with more columns?
Thank you!
Hey iris,
Sorry for not being clear. If you read the javadoc for what db.insert() does you’ll see that the second entry has the following description:
nullColumnHack – SQL doesn’t allow inserting a completely empty row, so if initialValues is empty, this column will explicitly be assigned a NULL value
Hence you should actually be able to put any column into that field (i.e. Notes.TITLE, etc) and basically it means that column will get instantiated to null if initialValues is passed in as empty.
Of course this should never happen, and it’s probably good practice if you check for empty insertions earlier and prevent it from getting inserted, but yes hope this answered your question.
hi jwei512,
i have created my own ContentProvider successfully. but now i want to use it in another android application. since i know the authority i did something like this
Uri books = Uri.parse(“content://com.sabretch.colorEyeD/color”);
Cursor cur = managedQuery(books, null, null, null, null);
but how can i find out the column details here in the query i want to pass a where clause how can i do that???
regards,
Mike
The first null in your call to managedQuery is a projection of the fields that you want to display. you cannot “discover” them, but if you specified “null” (as you have) you can loop through your cursor’s column index and get the column value by index. This useful for reverse engineering the CP.
Thanks for this tutorial .
I am new to android . I want store an image in content provider and retrive it .
please help me in this problem .
Thanks for this tutorial .
I am new to android , I want to store an image in cotent provider and retrive back ,
please help this problem , please send smple code .
Thank you.
Thanks for the helpful post on content provider
But I’ve one more question: How can we include several tables and query them in different activities within the same application?
Hey mamila,
Each Content Provider (as shown in the example) creates one table – so, to customize several tables just write different content provider classes for each one.
Now to query them in different applications, you’ll want to look into Cursors (http://stackoverflow.com/questions/903343/cursor-get-the-field-value-android). That’ll let you query them in different activities.
Hope this helps
– jwei
@jwei512, @mamila
Are you sure about that? Isn’t it possible to have different tables exposed via different URIs?
This seems to be what they are saying here:
http://developer.android.com/guide/topics/providers/content-providers.html#creating
Although I’m not certain.
I have the same problem as mamila and have yet to get a straight answer yet. I really appreciate your tutorial here, it’s been very useful, but I’m not sure about having one Content Provider per Table.
Your input would be appreciated!
Rich
Hey Rich / Mamila,
Sorry let me be more clear. It’s not that you can only have one table per content provider – you’re right in saying that you can expose different tables via different URIs which in theory can be created in the same content provider.
However in general (at least for me) it makes sense to keep it at one “sub” URI per content provider class (by “sub” URI I mean if it were content://jason.wei.apps/notes and content://jason.wei.apps/notes/titles as the two URIs then obviously these two are related and could/should go in the same content provider). So, for instance, if I had one table for notes and one table users, in theory I could write a single content provider that just does a bunch of case checking and uses a bunch of switch statements so that given the correct URI the correct query, etc, is performed. But for me it just makes sense to create some kind of NoteContentProvider.class and UserContentProvider.class to separate the two.
Does this make sense? Sorry if it seems like I’m rambling.
I really need to get some kind of forum for this stuff =P
– jwei
Yes. This is the same picture I had in my head, too. Thank you for replying to me and clarifying this.
Nice blog, I appreciate all the content here. Have bookmarked, will stop by again.
so sorry, i learn things quite slowly and kinda dont understand.
what path is this->
jason.wei.apps.notes.providers.NotesContentProvider(?)
if im doing my own content provider what is the path?
its the provider package.class name. you can user own…
Hi there! Jwei512, Ive created the whole ContentProvider and change it to my own. However, I still have errors!
I’m doing this contentprovider to show my data to a list view. I need your help.
If possible, please email me?
chuaisarocker@hotmail.com
thanks!
Hi! Really Good Blog, Giving Detailed information
Please may you tell me how to add more than One Content Provider to a single
mainfest file
Just simply register multiple providers – just as how you would register multiple Activities or Services in the manifest.
– jwei
Thanks jwei512 for a nice tutorial … 🙂
In the extension of ContentProvider, the query method has one issue which is sort of a strange design for me…
You create SQLiteDatabase instance and then query the content and then return the Cursor object. You never close the SQLiteDatabase instance.
For Android documentation it states the DB is cached, but it also states to close the DB…How and when does that happens??? Please let me know as I have written different applications and don’t want them to come out with some SQL Exception cause too many SQLiteDatabase instance exists in memory which are not closed.
Very good tutorial for new comer. I have one question how to use the our custom provider in another package. Can you please provide any document this how to use in another package.
As we are using android contact content provider.
Good!
I read the Busy Coder Guide to Android , online dev tutorials, but could not figure out to make a content provider. I got it cleared from here.
Very clean tutorial and example.
However, having the provider in
{package name}.providers.{Custom Provider name}
is not necessary.
AND, can you tell me is it possible to give restriction on the provider so nothing else than my application should access it?
Sorry for this very basic question. If I have a database on a remote server, can I still define that as a URI? Or do I have to create a SQLlite db on the Android and have THAT access the remote server?
If I CAN directly access the remote server, and again sorry to be dense, what would the db declaration look like?
Hey Emily,
Sorry I actually don’t know much about interacting with remote servers and remote databases.
In my experience, you can define a URI for local SQLite databases, but for remote servers/databases I’ve always used things like REST calls to access them.
Sorry if this didn’t help I wonder if anyone else would know about this…
– jwei
Thanks for your response.
I did implement it as a REST call, which works fine. Just thought it would be more elegant to do it as a ContentProvider.
Great tutorial!
Can I use the same method to overwrite an existing Android contentProvider? for example the Bookmarks?
I want to resort the Android Bookmarks db according to the user selected order (orderBy) – can this be done with overwriting the “cursor query” function?
hi
I am new in android i am stored student data in content providers
please send me code for my mail
Thanks&Regards
hemalatha
This is a really helpful tutorial – thanks. One question though:
You said, “One thing to note here is that Android requires that the unique id column have name “_id”.”
Does this mean that android doesn’t allow composite keys (i.e. where two columns in the table are combined to make the primary key) ? If so, is there a way around this – using the ProjectionMap somehow?
Hello Sir , this is very good example for learning . same thing i was trying. but when i execute my own content provider its showing this error
11-10 13:32:47.119: ERROR/ActivityThread(390): Failed to find provider info for com.xymob.bhilai.nameandfeedback
please give me any solution for that .
i was made one activity for insert the record through content resolver
thanks
tikam
Say I want a ListView populated from a custom database that has names and phone numbers. I could use an adapter to populate that list from a custom ContentProvider that I write. But my database is set up in two tables (person: _id, firstname, lastname) (number: _id, person_id, phonenumber). (I know this is a really bad example).
In this case I would want a Content Provider that returned a list that was the result of a JOIN of the two tables. So I would think that I could write my CP to return the result of the join, and then for a delete maybe would implement a delete from the number table, and for an add would add to the number table.
From the Activity’s point of view, it doesn’t know about the schema of the DB, that I’ve broken the data into two tables. It just knows that it can get a list of the people with their numbers and add/delete from the numbers.
I could also provide the CP to other apps if needed and they too would be shielded from the underlying DB schema.
Aside from my example being really lame, is this the intent of the ContentProvider? That I can expose logical content elements even if they don’t physically match the implementation in the DB (assuming I can make the delete/insert logic make sense for my app)? Or is it good practice to really have one CP for each table in the DB?
Thanks! Great article!
Hi! I’ve implemented this ContentProvider but I have an IllegalArgumentException Unknow URI content://….
Great tutorial, example is good explained, it helps a lot 😉
Hi,
Thanks for this nice tutorial. I just do not understand one thing. What’s the “CONTENT_TYPE” constant you set in the Notes helper class ? I mean, how do you know which value it should take ?
Thanks in advance
I would like to know this aswell.
Tremendues dude, very well explayned
Great tutorial, thanks. However, would suggest that you close your databases after you’re done using them via getWritableDatabase()/getReadableDatabase().
Thank you so much! Old post, but I am a newbie and this was what I needed for my job! Just wanted to thank you for the time putting the database stuff up!
thank you for this stuff
Pretty good! Thanks for your sharing!
There is one thing that I’m a little confused. What’s the meaning of “CONTENT_TYPE”? I mean what type it is? What’s your “vnd.android.cursor.dir/vnd.jwei512.notes” stands for?
Thanks in advance.
Would the ContentProvider be helpful to access and modify a database that’s stored on the SdCard? What would be the appropriate uri?
Thanks
very informative…
Thanx
Thanks for a very good tutorial
Hey,
just have two annotations to make:
1. You may have explicitly mentioned to put the -Tag into the -Tag of the Manifest and not just anywhere.
2. I get an Exception:
java.lang.ClassNotFoundException: jason.wei.apps.notes.providers.NotesContentProvider
I guess this is because your package path doesn’t really match your AUTHORITY as you said!?
See:
001 package jason.wei.apps.securenotes.providers;
034 public static final String AUTHORITY = “jason.wei.apps.notes.providers.NotesContentProvider”;
Can’t say I found the source of my problem through this or any other Tutorial concerning this Topic, but yours was the most simple and precise one and never the less it helped me, so thanks!
When does the onCreate() of DatabaseHelper class gets called ??
After or before onCreate() of content provider ?
Hey Priyanka,
It gets called before.
– jwei
Great information but I have one question you outlined that you use the “SQLiteDatabase to execute a CREATE TABLE statement where we build our table using our defined columns (i.e. Notes.NOTE_ID, Notes.TITLE, and Notes.TEXT) and also make sure we use the proper SQLite3 column types.” Then you said that Android required the unique id field to be “_id”, does tou “NOTE_ID” field fit this criteria?
Hey Darryl,
Yes – the NOTE_ID variable should be set to “_id”.
– jwei
Are there any tutorials on writing a ContentProvider if you already have a sqlite database that you are just reading from?
Hi Anuj,
Sorry don’t think I understand your question. Could you provide more details / rephrase it?
– jwei
Not a bad tutorial, but you have some code that won’t compile:
124 switch (sUriMatcher.match(uri)) {
125 qb.setTables(NOTES_TABLE_NAME); // this line and the one below it don’t belong here
126 qb.setProjectionMap(notesProjectionMap);
127 case NOTES:
128 break;
129 case NOTES_ID; // <- should be a colon
130 selection = selection + "_id = " uri.getLastPathSegment();
131 break;
132 default:
133 throw new IllegalArgumentException("Unknown URI " + uri);
134 }
And some code that's wrong:
066 switch (sUriMatcher.match(uri)) {
067 case NOTES:
068 break;
069 case NOTES_ID;
070 where = where + "_id = " uri.getLastPathSegment();
// Oops, you want a break here
071 default:
072 throw new IllegalArgumentException("Unknown URI " + uri);
073 }
Did you run this before posting it?
Hey Benson,
Yea I actually updated this post a few days ago (added the /uri/# path) so that it would be consistent with my new “Grant Uri Permissions” post. Unfortunately I updated it on the fly so didn’t get a chance to compile/run the new changes.
Thanks for catching these – should be fixed now.
– jwei
Hi
I have a custom content provider but how do I use it in another apk ? I tried importing the complete package but It is not accessible.
Please help
Nisha
Hi Nisha,
You simply need to pass the URI of your custom content provider into the query() methods – i.e. it doesn’t care which application is making the query as long as the URI is correct it will return a cursor pointing to the data.
– jwei
I’m not that much of a internet reader to be honest but your blogs really nice, keep it up!
I’ll go ahead and bookmark your website to come back later on. Cheers
inserting into the database works…have not encountered any errors there…querying doesnot work…throws an IllegalStateException…what am I doing wrong.