|
MyContentProvider |
Much have been written about contentproviders and there are various tutorials and examples available. I walk through many of those tutorials during my learning of custom contentprovider. However none of those tutorial gave me complete and clean implementation details.
Aim of this post is to show you a complete [all necessary function implementation] and clean [well organized code for better understading] custom contentprovider.
A contentprovider is primarily designed to use for data sharing between different application. A contentprovider provide a transparent interface for structured data storage. A contentProvider could be implemented with many different backends like SQLite, file storage or network storage. This tutorial addresses the implementation details of contentProvider implemented with SQLite database.
Lets start with an example. We have a Category class e.g
public class Category{
String name;
String status;
}
We want to insert and get these fields in contentprovider. For this will create a content discriptor for our content provider. Lets say it Mycontentdiscriptor:
public class MyContentDescriptor {
public static final String AUTHORITY = "sohail.aziz.mycontentprovider";
private static final Uri BASE_URI = Uri.parse("content://" + AUTHORITY);
public static final UriMatcher URI_MATCHER = buildUriMatcher();
private static UriMatcher buildUriMatcher() {
// TODO Auto-generated method stub
final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
// have to add tables uri here
final String authority = AUTHORITY;
//adding category Uris
matcher.addURI(authority, Categories.CAT_PATH, Categories.CAT_PATH_TOKEN);
matcher.addURI(authority, Categories.CAT_PATH_FOR_ID,Categories.CAT_PATH_FOR_ID_TOKEN);
return matcher;
}
public static class Categories {
// an identifying name for entity
public static final String TABLE_NAME = "categories/";
// the toke value are used to register path in matcher (see above)
public static final String CAT_PATH = "categories";
public static final int CAT_PATH_TOKEN = 100;
public static final String CAT_PATH_FOR_ID = "categories/#";
public static final int CAT_PATH_FOR_ID_TOKEN = 200;
public static final Uri CONTENT_URI = BASE_URI.buildUpon()
.appendPath(CAT_PATH).build();
public static class Cols {
public static final String cat_id = BaseColumns._ID;
public static final String key_2_catname="name";
public static final String key_3_catstatus="status";
}
}
}
Lets discuss the above code.
Authority: A content provider must have an Authority. Authority string must be included in manifext.xml file in order to use contentprovider. You can define it as you like.
UriMatcher : A uri matcher is used to match the URI's. Uri could be of a table or speceific row (record). For example in our case category table uri is:
content://sohail.aziz.mycontentprovider/categories
while uri for particular record in categories table could be:
content://sohail.aziz.mycontentprovider/categories/1
we defined two constants for these two different URI types in order to differentiate while querying.
Cols class is defining the fields of our Category object. First field of every table should be _ID. As its expected while binding to listView.
So far we were defining our content, lets create actual SQLite database to be used as backend with our contentprovider:
public class MyDatabase extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "mydatabase.db";
private static final int DATABASE_VERSION = 1;
// custom constructor
public MyDatabase(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
// TODO Auto-generated constructor stub
}
@Override
public void onCreate(SQLiteDatabase db) {
// TODO Auto-generated method stub
// creating tables categories
db.execSQL("CREATE TABLE " + MyContentDescriptor.Categories.TABLE_NAME+ " ( "+
MyContentDescriptor.Categories.Cols.cat_id+ " INTEGER PRIMARY KEY AUTOINCREMENT,"+
MyContentDescriptor.Categories.Cols.key_2_catname + " TEXT NOT NULL," +
MyContentDescriptor.Categories.Cols.key_3_catstatus + " TEXT," +
"UNIQUE (" +
MyContentDescriptor.Categories.Cols.cat_id +
") ON CONFLICT REPLACE)"
);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO Auto-generated method stub
if(oldVersion < newVersion){
db.execSQL("DROP TABLE IF EXISTS " + MyContentDescriptor.Categories.TABLE_NAME);
}
}
}
Here we defined out database name and write sql query for the creation of Categories table. (its not created yet).
Now come to the ContentProvider: We will use both of the above classes in our content provider. You can put all this code in Contentprovider class but its better to keep these separate for organization and readability. Lets create our contentprovider e.g MyContentprovider:
public class MyContentProvider extends ContentProvider {
private MyDatabase mydb;
@Override
public boolean onCreate() {
// TODO Auto-generated method stub
Context ctx = getContext();
mydb = new MyDatabase(ctx);
return (mydb == null) ? false : true;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// TODO Auto-generated method stub
SQLiteDatabase db = mydb.getWritableDatabase();
int token = MyContentDescriptor.URI_MATCHER.match(uri);
int count=0;
switch(token){
case MyContentDescriptor.Categories.CAT_PATH_TOKEN:
count= db.delete(MyContentDescriptor.Categories.TABLE_NAME, selection, selectionArgs);
break;
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
@Override
public String getType(Uri uri) {
// TODO Auto-generated method stub // returning self defined mime types
// to be used by other applications if any
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// TODO Auto-generated method stub
Log.d("sohail", "inside insert");
SQLiteDatabase db = mydb.getWritableDatabase();
int token = MyContentDescriptor.URI_MATCHER.match(uri);
switch (token) {
case MyContentDescriptor.Categories.CAT_PATH_TOKEN: // uri is of
// categories table
Log.d("sohail", "matched uri is CAT_PATH_TOKEN:" + uri.toString());
long id = db.insert(MyContentDescriptor.Categories.TABLE_NAME,
null, values);
// notifying change to content observers
getContext().getContentResolver().notifyChange(uri, null);
return MyContentDescriptor.Categories.CONTENT_URI.buildUpon()
.appendPath(String.valueOf(id)).build();
default:
throw new UnsupportedOperationException("URI: " + uri
+ " not supported.");
}
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// TODO Auto-generated method stub
Log.d("sohail", "query called");
SQLiteDatabase db = mydb.getReadableDatabase();
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
Cursor c;
int token = MyContentDescriptor.URI_MATCHER.match(uri);
switch (token) {
case MyContentDescriptor.Categories.CAT_PATH_TOKEN:
Log.d("sohail", "matched uri is CAT_PATH_TOKEN:" + uri.toString());
queryBuilder.setTables(MyContentDescriptor.Categories.TABLE_NAME);
c = queryBuilder.query(db, projection, selection, selectionArgs,
null, null, sortOrder);
return c;
case MyContentDescriptor.Categories.CAT_PATH_FOR_ID_TOKEN:
Log.d("sohail", "matched uri is CAT_PATH_TOKEN:" + uri.toString());
queryBuilder.setTables(MyContentDescriptor.Categories.TABLE_NAME);
queryBuilder.appendWhere(MyContentDescriptor.Categories.Cols.cat_id
+ "=" + uri.getLastPathSegment());
c = queryBuilder.query(db, projection, selection, selectionArgs,
null, null, sortOrder);
return c;
default:
Log.d("sohail", "no URI MATCHED");
return null;
}
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// TODO Auto-generated method stub
SQLiteDatabase db = mydb.getWritableDatabase();
int token = MyContentDescriptor.URI_MATCHER.match(uri);
int count=0;
switch(token){
case MyContentDescriptor.Categories.CAT_PATH_TOKEN:
count= db.update(MyContentDescriptor.Categories.TABLE_NAME,values, selection, selectionArgs);
break;
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
}
Mycontentprovider is exented from contentprovider and its methods: Insert,query,delete,update getType needs to be implemented. We create a database db in onCreate. This object is used in insert,query update and delete methods. Uri matcher is used to match the requested URI and on the basis of this uri, query is executed on particular table. (there could be more than one table). db.insert, db.update and db.delete are all sqlite funcitons. SQLite queryBuilder is used to query the specific table as indicated in URI. Results of the query are returned in Cursor object (which in-turn is used to populate UI e.g listView). On each insertion, new id of the inserted row is appended in URI and returned e.g after insertion of 3rth record the returned URI will be:
content://sohail.aziz.mycontentprovider/categories/3
this URI can be used to query the 3rd record (row) in categories table. Notice one thing that after insert, delete and update operation
getContext().getContentResolver().notifyChange(uri, null);
is called. This notifies the content observers that the particular table has been
modified. Thats all for the implementation of the contentprovider. Lets see how
can we use it in our activity. Lets say we have two edit text and a check box named
etID, etName and cbStatus.
private void InsertRecords() {
// TODO Auto-generated method stub
ContentValues conval=new ContentValues();
conval.put(MyContentDescriptor.Categories.Cols.key_1_catid, etID.getText().toString() );
conval.put(MyContentDescriptor.Categories.Cols.key_2_catname, etNAME.getText().toString());
String stat;
if(cbStatus.isChecked())
stat="true";
else
stat="false";
conval.put(MyContentDescriptor.Categories.Cols.key_3_catstatus,stat);
recent_uri=getContentResolver().insert(MyContentDescriptor.Categories.CONTENT_URI, conval);
Log.d("sohail","returned uri="+recent_uri);
//showRecords();
}
private void showRecords() {
// TODO Auto-generated method stub
cur= this.getContentResolver().query(MyContentDescriptor.Categories.CONTENT_URI, null, null,null, null);
String[] colums=new String[]{MyContentDescriptor.Categories.Cols.key_1_catid,MyContentDescriptor.Categories.Cols.key_2_catname};
//adapter= new SimpleCursorAdapter(this,android.l)
int[] to = new int[] { R.id.tvID, R.id.tvNAME };
adapter = new SimpleCursorAdapter(this, R.layout.list_item, cur, colums, to);
listview.setAdapter(adapter);
}
private void deleteAll() {
// TODO Auto-generated method stub
getContentResolver().delete(MyContentDescriptor.Categories.CONTENT_URI, null, null);
}
private void showRecent() {
// TODO Auto-generated method stub
Cursor cur2= this.getContentResolver().query(recent_uri, null, null,null, null);
adapter.changeCursor(cur2);
}
private void showchecked() {
// TODO Auto-generated method stub
boolean status=true;
String colname=MyContentDescriptor.Categories.Cols.key_3_catstatus;
Uri uri= MyContentDescriptor.Categories.CONTENT_URI;
String selection= MyContentDescriptor.Categories.Cols.key_3_catstatus+"=?";
Cursor cur3=getContentResolver().query(uri, null,selection, new String[]{"true"}, null);
Log.d("sohail",cur3.toString());
adapter.changeCursor(cur3);
}
Functions Description:
Insert: to insert new record.
showRecords: to show all records of categories table.
showRecent: to show the recently inserted record.
showChecked:to show all records where status=true.
deleteAll: to delete all records from categories table.
Browse and download source
MyContentProviderExample.
*Update* : Content provider does not provide synchronization by default, which means there can be synchronization issues if content provider is accessed (insert/update) from many threads/apps at once. However Sqlite does provide the synchronization. You need not to do anything if your ContentProvider is backed by Sqlite database. For detail explanation read SQLite, ContentProviders, and Thread Safety.