Figure 1.1 Content Provider and Content Resolver in Android
1. Background
1.1 Why Content Providers and Content Resolver
When you type "Android Content Provider" in Google search, you get tons and tons of results, tutorial, resources in Android Content provider. Invariably the resources start by a definition of what actually a content provider is and how can you use a Content provider.
In case you reached this article either through Google or from any other references, you might be tempted to ask "Why the hell is there another tutorial on Content provider?" You might be tempted to close this tab and move ahead. But if you really read above few lines and is still reading this line, then I ensure you that you wouldn't leave this tab without bookmarking.
In short, a Content Provider is Andoid's way of allowing apps to share their data with other apps.
Before I go into some technical aspects of what a Content provider is, let us first try to find an answer for "Why there is need of content provider? Why on earth would an app need data of another app?"
Now consider that you want to setup an appointment in an Appointment app. The day you have chosen is a holiday in your country( If you are in India, there will be plenty of them). It disturbs your schedule. How wonderful it would have been had your inbuilt calender app shared with you the holidays and your appointment scheduler could warn you about that? Of course that's what Ideally it should do. But how could the Appointment app that you have created will have this feature unless your calender app's data can be acquired by your appointment app?
Once you have created an appointment, you want an autonotification email to all concerned persons. Image what your app would be if every email id had to be searched in Gmail and pasted in your app? Would anyone use your app then? What user would expect? He would expect that your app fetches the email IDs automatically from contacts so that his task is reduced. In such cases again your app would need data from Contacts. Again- need of Content provider.
-"Ok, My app is not doing any of above and I do not want to write an appointment app anyhow. Leaving."
If that's what you wanted to say, wait for a sec. Do your app accesses images? Does it select images from storage? If it does, how do you access the images from an app when the file service is another system app? Answer is Content Provider.
-"Nah, I have no image app. I build games. All my game components are deployed with my app.Leaving".
Hello!! Please wait. Don't you have a social sharing of scores? If so, wouldn't you really want to present the player with an option to send an email to his game partners? :) Sure you do and you need to fetch contacts for that. You also may need to provide an interface when players can organize a tournament by utilizing calender provider's data.
-"But wait, so far you just clarified that how important it is for me to learn how to consume Contents from other apps, Shouldn't this topic be about Content consumer or something? I need not know how to create a notepad software for typing, for God sake I only need to know how to use it!"
Yes, you are partially correct. We should be talking about consuming contents. And that is called "Content Resolver" which is like a client application to a Content Provider. So yes Content resolver is important to learn. But if you are an app developer, you might be developing plenty of apps and you will surely want to utilize some data alreay collected by another app of yours to customize the content of a new app. For instance suppose you have a game and a particular user has purchased power boosters, don't you think it makes all the sense that your new Freemium app prompts for power booster rather than buying more life? Or for instance you have a "Calorie Meter" app where user inputs his calorie intake to keep track of his weight and calorie intake. Now you are planning to release a new App called 'Walk Meter". It helps in calculating the distance user has traversed and calculates calorie burnt based on that. How nice will it be to fetch that from your previous app about the user's weight, his daily intake of calorie and to tell him how much weight he has managed to gain lost?
So Content providers opens up fascinating features. It saves user's precious time that is spent on providing same information to different apps, it empowers your app to have access to great deal of data without having to acquire the data exclusively and it provides you an amzing capability to grow your apps in a niche!
If you had never heard or worked with Content providers then after reading through the background you would surely want to learn and learn it from an article which has made you believe why it should be learn't. If you already knew about Content Providers and have read through the background section, then you have done so becuase you still have things to learn about the topic.
So after a see through of what is Content Provider and Content Resolver and How the are useful and why they are used, it's time for us to finally reveal what we want to do about it?
There are plenty of tutorial around which goes on to show how can you write a ContentProvider and how to use them. But I have often being surprised at the lack of depth of those tutorials to actually help user understand the technical details of the methods and in what ways other scenerios be covered? Say for instance rarely you would come across an article which tells you what to do while fetching data from multiple tables? So in this tutorial our focus will be to help you build your own ContentProviders and ContentResolvers from scratch. We will provide logic, then break up the steps so that you can build them of your own.
So this tutorial will remain simple, compact and will implement ContentProvider for our OpinionMiningApp
1.2 Formal Introduction to Content Provider and Content Resolver
Recall from our learning of Android database and record handling that Android supports two types of storage: Internal and External. Android allocates between 100 Mb to 500 Mb space to all apps in it's file system. When an App choses to store it's data in the internal storage, data is hidden from user in the device and also in development environment unless the device is rooted. Now when an app choses to store it's data in internal storage, it's data can not be used by any other app. This is sort of providing the data services in a sandbox. This feature is one of the most important Android security features.
But when an App chooses to store the data in external storage( on any of sdcard sub directories) then that data is accessible by other apps or even by user.
Therefore in this current discussion we will focus on storage partaining to internal storage which can't be shared by other apps otherwise.
Another point needs a mention here. We know that Android provides a concept called SharedPreferences by means of which preferences can be shared across applications. But this has several limitations. One of the major limitations being that the Apps that shares data needs to have Parent-Child relationship for sharing SharedPreferences in No-Hack mode.
So we are left with only one option of utilizing data of one app from another is through Content Provider and Content Resolver interface.
Figure 1.1 gives an insight about the stuff that we will be learning here. An app can query data of another app, can insert new records in the app's data, can update or delete records using a Content Resolver interface if the other app provides access to it's data using a Content Provider.
2. Content Provider
While learning your first C program did you learn how to create a header file? Did you learn how to build a dll file when you were learning .Net? Or did you learn how to create a redistributable jar file component when you started learning Android?
If the answer to above question is no, then it makes all the sense to ask why should we start our lesson by learning content providers?
However as you can't write a C program without knowing header files and what they do and you can't build a simple Android application if you do not know the essential of header files, similarly we can't work with Content resolvers without knowing basics of Content providers.
A Content Provider is a class which extends android.content.ContentProvider class. So you can create a new class by name MySimpleContentProvider and make it extend to android.content.ContentProvider. Once you do that, it will prompt you to define the methods that are to be overridden due to inheritance. After doing it, you will see a class structure something bellow.
public class MySimpleContentProvider extends ContentProvider
{
@Override
public int delete(Uri arg0, String arg1, String[] arg2) {
return 0;
}
@Override
public String getType(Uri arg0) {
return null;
}
@Override
public Uri insert(Uri arg0, ContentValues arg1) {
return null;
}
@Override
public boolean onCreate() {
return false;
}
@Override
public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3,
String arg4) {
return null;
}
@Override
public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {
return 0;
}
}
The above implementation clarifies what is ContentProvider. It is a class that provides onCreate, update, query, insert, getType, delete. So what these methods do?
There are three ways of understing this. First you read junk of documents to know what they do, or secondly you start with an example to understand what they do, or you learn the trick to know it in an efficient and structured way. I am going to suggest you the third technique. So in order to understand what they do, just place your mouse over onCreate method, a popup description window will be opened. Press F2 . The window will be fixed. See the figure bellow.
Figure 2.1 Viewing the documentation of ContentProvider methods
One of the best things about Android is it is very well documented and I advise you to always look at the method, class or constructor definitions and descriptions in this way before being tempted to refer external documents.
When a client initializes an instance of any class which extends ContentProvider, the extending class's onCreate method is called. You might perform any initialization like that of opening database connections or setting up file connection in this code. But as referred, please do not perform lengthy or time consuming operations. Of the example of such operation would be to call a webservice to fetch data, or trying to obtain a large database contents as String[]. As onCreate is called synchronously, it will block the client untill it is completed. Therefore you must avoid resource intensive operations here.
Look this document also hints you that most of the database operations must be avoided till the call for query method. So let us understand what it does with it's documentation.
Figure 2.2 query method documentation
2.1 Understanding ContentProvider query() parameters
query is a method by means of which client requests for some data. For example let us consider OpinionData table that we used for SQLite section in our understanding data handling in Android.
So suppose if the OpinionMining app was to expose a content prvider through which our ShareSimple app could request for opinion data, then the queries would have to be in following few possible format in OpnionMiningContentProvider.
1) Select * from OpinionDataTable
2) Select [Words,Weight] from OpinionDataTable
3) Select Words,Weight from OpinionData table [where ( (Weight> 0) AND (Word like 'a%')) ] // Extracting positive opinion term starting with 'a'
4) Select Words,Weight from OpinionData table where Weight> 0 [order by Words asc]
5) Select * from OpinionDataTable where [No=1]
Recall that in our understanding data handling in Android we focussed on developing methods which are generic in nature. That is we tried to develop method in different storage types like file,xml, SQLite all in similar format.
I think now you could understand the objective of such generalization of the methods.
Now coming back to current discussion, as you can see any query could be one of the above formats where client may choose from any of the format. However the method must have provision for any of the formats.
2.1.1 Projection
in 2) we see that Client wants to know the information about particular columns. This is called projection. So in this case clients projection data would be
String[]projection=new String[]{"Word","Weight"};
In content provider, to form a dynamic query based on user's input we have to adopt a logic similar to the code bellow.
String[]projection=arg1;
String p="";
if(projection.length<=0)
{
p="*";
}
else
{
p=projection[0];
for (int i=1;i<projection.length;i++)
{
p=p+","+projection[i];
}
}
String Query="Select "+p+" from OpinionDataTable"
2.1.2 Selection
The third argument to query method is selection. Here all client has to do is send entire string other than the word "where". So if client wanted to select OpinionDataTable based on precisely the same condition as in 3) then the argument string will be "where ( (Weight> 0) AND (Word like 'a%'))".
This can be done in two ways: either client can keep
String selection= "where ( (Weight> 0) AND (Word like 'a%'))";
String[]selectionArgs=null;
Because in string selection there is no variables. Or it can leave the scope for variables in selection query and specify the values through selectionArgs array.
String selection= "where ( (Weight> ?) AND (Word like ?))";
String []selectionArgs=new string[]{"0","'a%'"};
Here "?" will inform the content provider as to where the values from selectionArgs are to be appended.
Now remember while writing your ContentResolver, you may not always be knowing exactly how corresponding ContentProvider is built as that might be a third party ContentProvider. So you have to assume that universially selection arguments are sent in second way. However while writing your own ContentProvider you must provide a provision for both of them.
So our modified content provider which takes selection also into account is as bellow.
String[]projection=arg1;
String p="";
if(projection.length<=0)
{
p="*";
}
else
{
p=projection[0];
for (int i=1;i<projection.length;i++)
{
p=p+","+projection[i];
}
}
String wherePart="";
String selection=arg2;
String []selectionArgs=agr3;
if(!selection.isEmpty() && selectionArgs.isEmpty())
{
wherePart=selection;
}
else
{
wherePart=selection.replace("?","%s");
wherePart=String.format(wherePart,(Object[])(selectionArgs));
}
String Query="Select "+p+" from OpinionDataTable where "+wherePart;
2.1.3 sortOrder
For Client, you need to send just the variables and sort order and need not to frame "order by" term in orderBy variable. So if (5) was client's query,
String orderBy=" Words ASC";
At the ContentProvider, the query formation will now be modified to incorporate orderBy string into query string.
String[]projection=arg1;
String p="";
if(projection.length<=0)
{
p="*";
}
else
{
p=projection[0];
for (int i=1;i<projection.length;i++)
{
p=p+","+projection[i];
}
}
String Query="Select "+p+" from OpinionDataTable";
String wherePart="";
String selection=arg2;
String []selectionArgs=agr3;
if(!selection.isEmpty() && selectionArgs.isEmpty())
{
wherePart=selection;
}
else
{
wherePart=selection.replace("?","%s");
wherePart=String.format(wherePart,(Object[])(selectionArgs));
Query="Select "+p+" from OpinionDataTable where "+wherePart;
}
String orderBy=arg5;
if(!orderBy.isEmpty())
{
Query="Select "+p+" from OpinionDataTable where "+wherePart+" order by "+orderBy;
}
Once the query is formed you can utilize it in different ways and for different data types. Now there are few very interesting thing no notice.
Suppose you were using a Flat File database or XML database or SharedPreferences then relational query like one shown from 1) to 5) does not exists. You need to perform the operation through programming logic. Only in the cases of SQLite you can form a query from selection pareameters and execute that. But interestingly if you use SQLite, then you will be using following code.
SQLiteDatabase db=SQLiteDatabase.openDatabase(DB_PATH + DB_NAME, null, SQLiteDatabase.OPEN_READONLY);
Cursor dbCursor = db.query(tableName, null, null, null, null, null, null);
Where db.query has following parameters.
Figure 2.3 : SQLiteDatabase object's Query Method
So you can see you can simply execute the query without requiring to go through above logic to combine the parameters into a single query.
String [] projections=arg1;
String selection=arg2;
String[]selectionArgs=arg3;
String groupBy=null;
String having=null;
String orderBy=arg4;
Cursor dbCursor = db.query("OpinionMiningTable", projection, selection, selectionArgs, null, null, orderBy);
It is that simple!
But then why would we have to learn the technique to form query string from parameters. Well, consider following query
"Select A.No,B.Name from A,B where A.No=B.No"
Where A and B are tables which have No,{No,Name} columns respectively. How would you frame the query in SQLite which exprects a single table name?
In such cases you need to use rawQuery() which can execute any query involving any number of tables or views or both.
So
Cursor dbCursor=db.rawQuery(queryString,null);
Will return same result.
2.1.4 uri
Observe our discussion throughpit the section for ContentProvider part. You may notice that while forming query method we have assumed TableName as default "OpinionMiningTable". However your content provider may deal with more than one table. Also note that SQLiteDatabase db declaration we have a parameter called DB_PATH which would be invariably "data/data/com.integratedideas.opinionmining/databases/" if OpinionMining was using internal storage. If would be "sdcard/emulated0/Pictures/OpinionMining" if it was using external storage in OpinionMining directory inside Pictures directory. It might for that matter use any other directory or subdirectories. So through Uri, client should specify the exact path of the location of database.
Technically the uri field should be in format:
content://<authority>/<path>/<OPTIONAL_id>
where:
content:// is keyword and must be constant
<authority>: is the package which is providing the content. so in above case <authority> must simply be replaced with com.integratedideas.opinionmining. In any natural case you must interpret authority in the same way.
<path>: It is invariably the <database name>/<TABLE_NAME>. Now ContentResolver is not meant to know where and how content provider is storing data. So <path> simply means database name in case of SQLite, file name in case of Flat file data, XML file name in case of XML data and name of SharedPreferences in case of SharedPreferences.
<OPTIONAL_id>: it is particular record number. Say if client wants to extract OpinionDataTable record with No=5, then uri becomes:
content://com.integratedideas.opinionmining/OpinionSQLiteDatabase/OpinionWordTable/5
where OpinionSQLiteDatabase is the name of the database that holds table OpinionWordTable. As query method already supports selection, more often or not this field is not required. In any case, the ContentResolver class must parse the uri, extract string, extract the fields and accordingly form the query.
2.2 Developing ContentProvider
We shall now develop a content provider for SQLite database of OpinionMining Project. First download the project and import it to eclipse from here>>
Now create a new class by name ContentProviderForSQLite and make it extend ContentProvider.
Finally update the query method. The class with unused methods removed is shown bellow.
public class ContentProviderForSQLite extends ContentProvider
{
public static String DB_PATH = "/storage/emulated/0/Pictures/OpinionMining/";
public static String DB_NAME = "%s";
private SQLiteDatabase db;
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder)
{
String id=null;
String a=uri.getAuthority();
List<String> pathSegs=uri.getPathSegments();
if(pathSegs.size()>2)
{
id=uri.getLastPathSegment();
if(selectionArgs.isEmpty())
{
selectionArgs=new String[]{id};
selection="No";
}
else
{ selection=selection+" AND No=?";
String [] tmp=new String[selectionArgs.length+];
for(int i=0;i<selectionArgs.Length;i++)
{
tmp[i]=selectionArgs[i];
}
tmp[tmp.length-1]=id;
}
}
DB_NAME=String.format(DB_NAME, pathSegs.get(0));
String tableName=pathSegs.get(1);
db=SQLiteDatabase.openDatabase(DB_PATH + DB_NAME, null, SQLiteDatabase.OPEN_READONLY);
Cursor dbCursor = db.query(tableName, projection, selection, selectionArgs, null, null, sortOrder);
return dbCursor;
}
}
As you can see that it is role of the ContentProvider to parse the uri and obtain authority, Database path and Table name from uri. If uri contains id then it should also extract that.
Now when you see this implementation it looks too different from amny or should I say most of the resources you see over internet on this subject would never tell you how to construct ContentResolver class for most practical purposes. What we have learnt in 2.1.1 will be of immense help.
2.2.1 Forming the Query Method
String id=null;
String a=uri.getAuthority();
List<String> pathSegs=uri.getPathSegments();
if(pathSegs.size()>2)
{
id=uri.getLastPathSegment();
}
So if your Uri does not have id, it is: DatabaseName/tableName format. If it has id then: database/table/id format. getPathSegments() returns all parts of the path. So if you have id, there will be more than two strings ( a third id string) and if you have id in url you can extract that by extracting getLastPathSegment();
Now if Id is present that means your selection criteria ( if any) should be modified.
Suppose your Id is not unique, all words with a have Id=1, words staring with b have Id=2 and so on selection criteria was "where abs(weight)>2 And Id=1"
But as user has specified 1 in Id, his selection field will be "abs(weight)>?" and selectionArgs=new String[]{"2"}. So at the ContentProvider this needs to be modified and Id has to be integrated to it such that selection becomes
"abs(weight)>? And No=?" and selectionArgs=new String[]{"2",Id}
On the other hand if user had not specified any selection criteria( which is a common case if record number is specified) then selection and selectionArgs will be like:
selection="No=?" selectionArgs=new String[]{Id};
So in first case the whole selection string has to be modified and a new array has to be declared where new Id value has to be appended with existing array content. Second case is straight forward with creation of new selection string as well as selectionArgs array.
if(selectionArgs.isEmpty())
{
selectionArgs=new String[]{id};
selection=" No=?";
}
else
{ selection=selection+" AND No=?";
String [] tmp=new String[selectionArgs.length+];
for(int i=0;i<selectionArgs.Length;i++)
{
tmp[i]=selectionArgs[i];
}
tmp[tmp.length-1]=id;
}
Now last part is to extract Database and table name and executing the query.
DB_NAME=String.format(DB_NAME, pathSegs.get(0));
String tableName=pathSegs.get(1);
db=SQLiteDatabase.openDatabase(DB_PATH + DB_NAME, null, SQLiteDatabase.OPEN_READONLY);
Cursor dbCursor = db.query(tableName, projection, selection, selectionArgs, null, null, sortOrder);
2.2.2 A Note about selecting data from Multiple Tables
If it so happens that you have to fetch the data from multiple tables, it is always a wise option to create a view and then selection from View. Take for the example of table A and B we discussed above. Using SQLite browser or using createTeable query we can create a view:
create view ABmyView_view as ( select A.Id, B.Name from A,B where A.Id=B.Id)
Then in URI pass the view name in <tableName> part of Uri. "Select * from ABmyView" query will return exactly the same result as that of query specified in bracket.
For inserting set of data to multiple tables, you have to call insert method for every table with that table specific Uri and parameters.
2.2.2 Exposing the ContentProvider
Well we have so many classes inside OpinionMining project. Moreover the application package is set to start MainActivity when the app is launched. However the main activity does not extend ContentProvider and it never should( because your goal is not to launch another app but to fetch it's data). So how do you expose the ContentProvider?
Well, through the manifest. So edit your AndroidManifest.xml and add following tags inside application tag and outside any activity tag. I always prefer it to be put just before the </application> tag to make sure that it is at the correct place.
<provider android:name=".ContentProviderForSQLite"
android:authorities="com.integratedideas.opinionmining"
android:exported="true"
android:grantUriPermissions="true" />
android.name is the name of your class. Always remember that the class which is extending the ContentProvider must be public.
android.autherities should be the package name. Well technically it can be any name, but package name will save you from any confusion.
exported=true makes it discoverable to other apps.
grantUriPermission enables it's methods to be called through uri.
Just to be sure that you have got the manifest file right, here is the complete dump of it.
="1.0"="utf-8"
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.integratedideas.opinionmining"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="14" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.integratedideas.opinionmining.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=".ContentProviderForSQLite"
android:authorities="com.integratedideas.opinionmining"
android:exported="true"
android:grantUriPermissions="true" />
</application>
</manifest>
That's it. Now just install it in your device.
3. ContentResolver
A ContentProvider when exposed is exposed globally to entire Android system. When you declare any activity class, by default it can get an instance of a ContentResolver() by calling getContentResolver() method which can access any ContentProvider's contents. So for utilizing the services of the above OpinionMining content provider all you have to do is use the following lines in any client app from the part from where you want to test and test it;
try
{
String s="content://com.integratedideas.opinionmining/OpinionSQLiteDatabase/OpinionDataTable";
Uri uri=new Uri.Builder().build().parse(s);
Cursor cursor = getContentResolver().query(uri, new String[]{"Word", "Weight"}, "Weight > ?", new String[]{"0"}, "Word ASC");
while(cursor.moveToNext())
{
String word=cursor.getString(0);
String weight=cursor.getString(1);
Log.i(word,weight);
}
}catch(Exception ex)
{
}
Though there are more flags and stuffs to know in ContentResolver, but trust you above few lines is enough to know for most of applications which involves fetching data from ContentProviders.
However do know that ContentResolver can be instantiated from a valid Context only. So getContentResolver() call can be made only in an activity class. But what if you want to call this from a method in a non activity class?
In such cases, you got to have a Context object in the class which must be instantiated through the activity by supplying the context of the activity. Then getContentResolver() can be called through that context.
public class MyClass
{
public String someMethod(Context context)
{
context.getContentResolver();
}
}
public void onCreate()
{.
.
.
MyClass m=new MyClass();
m.someMethod(this);
}
4. Using Database Record Modifying Methods
4.1 InsertMethod
First let us see the autocreated insert method and how it looks.
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
We already know uri part. Now we need to understand ContentValues part.
Before understanding how Insert method should be framed in ContentProvider and how it must be resolved in ContentResolver, let us borrow the InsertMethod record we developed for our OpinionMining App in section 4.2.4
public int InsertRecord(String tableName,String []newData)
{
db=SQLiteDatabase.openDatabase(DB_PATH + DB_NAME, null, SQLiteDatabase.OPEN_READONLY);
Cursor dbCursor = db.query(tableName, null, null, null, null, null, null);
String[] columnNames = dbCursor.getColumnNames();
db.close();
ContentValues values = new ContentValues();
for(int i=0;i<columnNames.length;i++)
{
values.put(columnNames[i], newData[i]);
}
db=SQLiteDatabase.openDatabase(DB_PATH + DB_NAME, null, SQLiteDatabase.OPEN_READWRITE);
db.insert(tableName, null, values);
db.close();
return 1;
}
It is too obvious that while inserting, you need to provide set of key value pairs, where keys represents the column names and value represents their new value.
Column names of a table might not always be known to the client. Therefore client should ideally call query method and obtain the Column names from the cursor. Then it must append the new values with the column names just like this loop:
ContentValues values = new ContentValues();
for(int i=0;i<columnNames.length;i++)
{
values.put(columnNames[i], newData[i]);
}
Once uri and ContentValues is available before the insert method, it should execute the following section.
db=SQLiteDatabase.openDatabase(DB_PATH + DB_NAME, null, SQLiteDatabase.OPEN_READWRITE);
db.insert(tableName, null, values);
db.close();
So the insert method that we had developed for our SQLite section of OpinionMining will now be divided into two parts L first block should be done in client and the second one in the insert method. But insert method should also have the uri parsing section we developed above to fetch the table name and the database name from the uri section. Also as this is not selection, uri will contain only two parts.
So our insert Method of ContentProvider goes as bellow:
@Override
public Uri insert(Uri uri, ContentValues values) {
List<String> pathSegs=uri.getPathSegments();
DB_NAME=String.format(DB_NAME, pathSegs.get(0));
String tableName=pathSegs.get(1);
db=SQLiteDatabase.openDatabase(DB_PATH + DB_NAME, null, SQLiteDatabase.OPEN_READWRITE);
int id=(int)db.insert(tableName, null, values);
db.close();
Uri newUri= Uri.parse(uri.toString()+"/"+id);
return newUri;
}
And the client part will be:
Log.i("Insert Query","calling");
String []newRow=new String[]{null,"tension","-2"};
String s="content://com.integratedideas.opinionmining/OpinionSQLiteDatabase/OpinionDataTable";
Uri uri=new Uri.Builder().build().parse(s);
Cursor cursor = getContentResolver().query(uri, null, null,null, null);
String []columns=cursor.getColumnNames();
String cols="";
ContentValues values=new ContentValues();
for(int i=0;i<columns.length;i++)
{
cols=cols+ " "+columns[i];
values.put(columns[i], newRow[i]);
}
Log.i("Columns are:",cols);
uri=getContentResolver().insert(uri, values);
Log.i("Result of Insert",uri.toString());
Here is the logcat screenshot of getting insert query to work for the content resolver.
Figure 4.1: Result of Insert query
4.2 Update and Delete
Once query and insert methods are understood the remaining methods becomes far easy to understand. Here is how update method works:
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs)
{
List<String> pathSegs=uri.getPathSegments();
DB_NAME=String.format(DB_NAME, pathSegs.get(0));
String tableName=pathSegs.get(1);
db=SQLiteDatabase.openDatabase(DB_PATH + DB_NAME, null, SQLiteDatabase.OPEN_READWRITE);
int n=db.update(tableName,values, selection, selectionArgs);
db.close();
return n;
}
I guess there isn't anything that you wouldn't understand in the above query formation now. The client part of update is also pretty simple. It's about passing the new value and selection parameter.
Suppose I want to alter the weight of the word "tension" we inserted in last subsection. Our SQL query would be:
update OpinionDataTable set ( Weight=-4) where Word like 'tension'
Hence :
String selection="Word like ?";
ContentValues updateValue=new ContentValues();
updateValue.put("Weight",-4);
n=getContentResolver().update(uri, updateValue,selection,selectionArgs);
Log.i("Number of Rows Affected=",""+n);
Here goes the delete method
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
List<String> pathSegs=uri.getPathSegments();
DB_NAME=String.format(DB_NAME, pathSegs.get(0));
String tableName=pathSegs.get(1);
db=SQLiteDatabase.openDatabase(DB_PATH + DB_NAME, null, SQLiteDatabase.OPEN_READWRITE);
int n=db.delete(tableName, selection, selectionArgs);
db.close();
return n;
}
And it's calling in the client:
String selection="Word like ?";
String[]selectionArgs=new String[]{"tension"};
int n=getContentResolver().delete(uri, selection, selectionArgs);
Log.i("No of rows deleted",""+n);
Figure 4.2: Result of Calling ContentProvider methods from ContentResolver
5. Android Ready ContentProviders
We learnt the power of ContentProvider and how it helps accessing data which is already collected from user and is already present in the device by other apps. Android comes with plenty of "apps". For example contacts, calender, alarm clock, call log services etc. Wouldn't it be nice if you get access to those data readily?
Android makes it quite easy. It has a package called provider . It exposes data of several data through provider classes. So what are the ready content provider one can have?
All you have to do is in any of your methods say for example onCreate method of an activity class just type
android.provider
Eclipse pops you with all the providers which are available. See figure 5.1 for the view
Figure 5.1: Ready Android ContentProvider
UserDictionary,Browser,ContactsContract,Settings,AlarmClock,CalenderContract, MediaStore are some of the ContentProviders. So you can work with them. In this section we will work with ContactContracts which provides data from Contacts.
5.1 Working with Contact ContentProvider
For working with any ContentProvider in provider package, you need to provide permission to your app through AndroidManifest.xml. For working with contacts and to be able to read and modify contacts you should provide following permissions:
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
The Uri for Contacts is:
content://com.android.contacts/contacts
So what are the fields about which we can fetch details? We already learnt how to call a query method of A ContentProvider. We also know that a query returns cursor and from cursor we can get the column names.
So we can write a code block to fetch the column names of the contacts.
Cursor cur = getContentResolver().query(Uri.parse(content:), null, null, null, null);
String[]columns=cur.getColumnNames();
String s="";
for(int i=0;i<columns.length;i++)
{
s=s+columns[i]+" ";
}
Log.i("Columns",s);
However an important question at this stage is how on earth are we supposed to know the Uris? Would we have to refer to some external documentations?
Not at all. Every ContentProvider class in Android comes with a static CONTENT_URI field of Uri type. All you have to do is pass this in Uri field of query or other methods.
So :
Cursor cur = getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
This incidently will also return all the data about Contacts. See the image bellow about the LogCat trace.
Figure 5.2: Getting the Column Names of Contacts
Interestingly you can see a column by name display_name which suggests the name of the Contact. But you do not find anything like phone_number or email. That is because they are all stored in different tables. Because of inherent nature of ContentProviders, it is not possible to fetch data from multiple tables. So all we have to do check if the person has_phone_number, and if so fetch the details from phone data by selecting through the current id.
So logically if you want to extract all the Contacts, their names, Phone number and email addresses, you would have to write nested operation. Open a cursor of contacts, find if he has number, if so open phone table and find phone number and then open Email table and select the data based on id selection.
Here is one awesome method which returns all your Contacts ( name, phone, email). But remember because of three nested whiles, this is very very time consuming process and you must use Executor or AsyncTask to execute them.
public ContactPersonDetails[] FetchAllContacts()
{
ArrayList<ContactPersonDetails> allContacts=new ArrayList<ContactPersonDetails>();
ContactPersonDetails person=new ContactPersonDetails();
ContentResolver cr = context.getContentResolver();
String [] projections=new String[]{ContactsContract.Contacts._ID,ContactsContract.Contacts.DISPLAY_NAME,ContactsContract.Contacts.HAS_PHONE_NUMBER,ContactsContract.CommonDataKinds.Phone.NUMBER,ContactsContract.CommonDataKinds.Email.DATA};
Cursor cur = cr.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
if (cur.getCount() > 0)
{
while (cur.moveToNext())
{
String id = cur.getString(
cur.getColumnIndex(ContactsContract.Contacts._ID));
person.ID=id;
person.Name = cur.getString(
cur.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
person.Phone="";
if (Integer.parseInt(cur.getString(cur.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER))) > 0)
{
try{
Cursor pCur = cr.query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID +" = ?",
new String[]{id}, null);
while (pCur.moveToNext())
{
person.Phone = pCur.getString(
pCur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
}
pCur.close();
}catch(Exception ex)
{
}
}
try
{
Cursor emailCur = cr.query(
ContactsContract.CommonDataKinds.Email.CONTENT_URI,
null,
ContactsContract.CommonDataKinds.Email.CONTACT_ID + " = ?",
new String[]{id}, null);
while (emailCur.moveToNext())
{
person.Email = emailCur.getString(
emailCur.getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA));
String emailType = emailCur.getString(
emailCur.getColumnIndex(ContactsContract.CommonDataKinds.Email.TYPE));
}
emailCur.close();
}catch(Exception ex)
{
}
allContacts.add(person);
person=new ContactPersonDetails();
}
}
ContactPersonDetails [] contactsArray=new ContactPersonDetails [allContacts.size()];
contactsArray=allContacts.toArray(contactsArray);
return contactsArray;
}
Where ContactPerson details is a simple class defined as bellow:
public class ContactPersonDetails
{
public String ID;
public String Name;
public String Email;
public String Phone;
public static String GetIdFromNameInPersonArray(String name,ContactPersonDetails[]cpd)
{
for(int i=0;i<cpd.length;i++)
{
if(cpd[i].Name.equals(name))
{
return cpd[i].ID;
}
}
return null;
}
}
Here is a screenshot of my contacts
Figure 5.3: LogCat of All Contacts
Now if you are interested to know how to apply projection, selection and sorOrder on ready content providers, here is a method:
public String[] FetchContactNamesAsProjectionAndSelectionCriteriaWithOrderingDemo(String searchPattern)
{
ArrayList<String> allNames=new ArrayList<String>();
ContentResolver cr = context.getContentResolver();
String [] projections=new String[]{ContactsContract.Contacts._ID,ContactsContract.Contacts.DISPLAY_NAME};
Cursor cur = cr.query(ContactsContract.Contacts.CONTENT_URI, projections, ContactsContract.Contacts.DISPLAY_NAME + " LIKE ?",
new String[] {"%"+searchPattern+"%"}, ContactsContract.Contacts.DISPLAY_NAME + " ASC");
if (cur.getCount() > 0)
{
while (cur.moveToNext())
{
String id = cur.getString(
cur.getColumnIndex(ContactsContract.Contacts._ID));
String Name = cur.getString(
cur.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
allNames.add(Name);
}
String[] person=new String[allNames.size()];
person=allNames.toArray(person);
return person;
}
return null;
}
Projection array can be formed by looking at the column names we found out at the beginning of this section.
5.2 Working With Readymade Intents for Content Providers
Suppose you want to collect some user data as contact from your app. What you do? Will you design a whole new form for Contacts? Android provides you an easier option. It provides you tons of readymade Intents which can be used to intereact with the Content provider. We present an example of Inserting Contacts through Intent.
public void InsertContactThroughIntent(String name, String email, String phone)
{
Intent intent = new Intent(Intent.ACTION_INSERT);
intent.setType(ContactsContract.Contacts.CONTENT_TYPE);
intent.putExtra(ContactsContract.Intents.Insert.NAME, name);
intent.putExtra(ContactsContract.Intents.Insert.PHONE, phone);
intent.putExtra(ContactsContract.Intents.Insert.EMAIL, email);
context.startActivity(intent);
}
All you have to do is call this method and it opens up an Intent form where you can pass details collected from your app or just leave it as null and insert it in the form itself.
Figure 5.4: Adding New Contact Through Intent
6. Conclusion
While I was learning Android, more often or not I came across with tutorials which were very rich in contents but poor in guiding how to get things done. While writing this tutorial I tried my level best to be as specific about the technicalities of the ContentProviders as I can. With this tutorial I have attached a Client app.Download ContentResolverClient_App.zip
It is a nice little utility to pull Address names in a Auto Complete text and displaying the photo of the contact that you selected( if he has a photo). The onCreate and onClick sections has all the client part calling that we discussed in this tutorial. You can uncomment specific sections to know how the section is working!
Figure 6: Screenshot of Client App | | |
| |
| |