Tuesday, March 11, 2008

Observing content

Android documentation makes the point about content providers that they are perfect tool for hiding data access details. The Android content provider API has a clever little feature, however, that allows applications (and services) to observe changes of a dataset. The use case is that some component manipulates the persistent dataset that other components depend upon. The usual pattern is that the component makes the changes then invokes some sort of notification interface so that the dependent components are aware that the dataset was changed. If such a strong coupling is not possible, the dependent components may even need to poll for changes.

The Android content provider framework allows a much more elegant design. As datasets are identified by unique URIs, it is possible to ask for notifications if a certain URI is changed. The framework is the following.

  1. Well-behaving content providers are expected to notify the content resolver if they do something that may potentially change their dataset. This includes insert and update operations but registering database cursors returned by query operations is also necessary because cursors may be used to update data.
  2. Components interested in dataset changes register at the content resolver. If any URI is manipulated that they registrated for, they get notification.
You can download the example program from here.

Click here to access the example program adapted to Android SDK 2.3.3.

The content observer framework depends on the cooperation of content providers and content observers. Content providers are expected to notify content resolver that they have updated the dataset. You can observe this in our well-known simple provider, SimpleStringDataProvider.java.

In insert method:

getContext().getContentResolver().notifyChange(uri, null);
This notifies all the observers registered for that particular URI that change happened.

In query method:

c.setNotificationUri(getContext().getContentResolver(), url);

where c is reference to a Cursor object. If the data behind the cursor's position is updated (Cursor.update*(), Cursor.commitUpdates()), the given URI will be notified.

The other side of the picture is the observer. In this simple example, the observer is located in the DPObserver activity. As the observer is unregistered using the observer object reference, the observer is registered at onStart() and unregistered at onStop() because after onStop() the state of the activity may be lost.

In registerContentObservers:

ContentResolver cr = getContentResolver();
stringsObserver = new StringsContentObserver( handler );
cr.registerContentObserver( SimpleString.Strings.CONTENT_URI, true, stringsObserver );


We requested notification for the content URI allocated to the Strings provider plus all its descendants. This is important because e.g. the Strings provider generates URIs for newly allocated items like the following: content://aexp.dpobserver.SimpleString/strings/[id] where [id] is an integer number. These URIs are not equals to the Strings provider's base URI, these are descendants of the base URI.

The observer object is a child of the android.database.ContentObserver class. Its onChange() method is called by the content resolver if there is a change of the data behind the URI the observer was registered for. Unfortunately the URI that triggered the invocation is not passed to the method. The onChange() method is called in the context of a Handler. In our case, this Handler uses the UI thread of the activity.

You can try the application by launching the DPObserver activity and monitoring the log (adb logcat from the command prompt). Whenever you add a new data item (by clicking a menu item), the onChange() will generate a log message.

Now comes the interesting bit. We actually registered another observer, that one for the URI of content://contacts/people and descendants which belongs to the Contacts application. Launch now the Contacts application and add a new user. You will see in the log that our activity got called (except if it was stopped by the application manager because it went into the background). At this point, we could access the Contacts content provider and find out, what entry was added. It would be much more easier if we had the URI of the added entry but finding the new entry is possible by looking for the entry with the highest ID. We could hook onto Contacts database manipulations with our own functionality. Isn't that interesting? ;-)

24 comments:

Anonymous said...

Hey! Thanks for writing about this. You solved a question i had previously posted on the android-dev list and stackoverflow.com but got no good answers :-(

http://stackoverflow.com/questions/230643/android-api-for-detecting-new-media-from-inbuilt-camera-mic

Kamal Hasan said...

It seems your code is based on m5 version. Can you share the same for 1.0 version.

Dani said...

Article is good but I'm searching to know which data was eliminated... before to commit changes.

I read and ask in a lot of internet pages, but i don't have a solution.
I think that's no possible create your own "triggers" in this sandbox android framework. It's a bad idea of android company, no let create triggers in intern databases.

Thank you in advance,

Kiran Wali said...

Hi,
This is indeed a nice article. I am new to Android Development and wana to learn it..
Please let me know.. from where to start..
Thanks in Advance..

Anil Philip said...

this was a life saver for our team! need a job? ;)

Gabor Paller said...

You are offering a research job in Hungary? I might be interested. ;-)

Anonymous said...

Thanks for this posting.. Very useful.

Anonymous said...

Do you have any example of applying ContentObserver on a Cursor through cursor.registerContentObserver() ?

For some reason registering for content observer through Content Resolver works but through cursor doesn't work for me.

Anonymous said...

Assuming an app is based on camera data, new data can be added by camera only after onStop() of the app has been called, how could the app observe for changes if the listener is unregistered in onStop() ?

I understand why you unregistered it in onStop() but in case of above scenario ?

Anonymous said...

Hey Man nice blog and indeed nice topic you touched, i have a question when we receive notification via contentobserver it is easy to find out added use case and deleted use case , how would i come to know which contact is edited in efficient way. if you have any idea please shed light on it

Gabor Paller said...

Hi, Anonymous, I checked the ContactsProvider code and it does call notifyChange() on the URI in case of update(). Don't you receive that notification?

himanshu said...

Hey, i know it calls onChange() method in content observer on either of the use cases i.e. delete/add/edit but my question is can we get to know which row exactly or contact id has been edited , in case of delete we can check delete flag in raw_contacts and as u mentioned for add find highest id , what about if something has been updated , i want to that row .. thanks if you can give any directions.

Gabor Paller said...

Himanshu, the only way I could figure out is to check the _sync_dirty column, That column is set to 1 if the row is modified. Sadly, it is not cleared until the next sync so you have to remember, where you saw _sync_dirty=1 values before and it still does not solve the multiple update problem.

It would be so much easier if onChange received the content URI. :-(

himanshu said...

@Gabor,

Thanks for the prompt reply.., do have any idea about SyncAdapter in Google I/O conference they mentioned about it. I can see example that sync content at cloud to platform. what if you want the content from raw_contact table does SyncAdapter get it through ContentProvider api ?.

i think instead rely on COntentObserver i can use SyncAdapter because it automatially get how many rows affected but still my question is where it get the data of raw_contact via ContentProvider??

thanks

Aditya Pathak said...

Can you tell me how to use content observer in manifest file

Mr. Wendel said...

I appreciate your concise example. In particular, this isn't as readily discovered in other Content Provider examples:
c.setNotificationUri(getContext().getContentResolver(), url);

Albattya Galbattya said...

Thanx for ur post. I have a different issue. I am observing the browser.db which stores both history & bookmarks. My requirement is that i want to get notified only if a BOOKMARK is added/updated/removed & update my bookmarks list. But my listener gets fired even if a new page loads into the browser ( coz this comes in history). Any way to resolve this ? or to observe changes for a single column. I dont want to unnecesary update my list on every event fired. Thanx in advance.

PuZZleDucK said...

Ash: check out this one:
http://forum.xda-developers.com/archive/index.php/t-475892.html

Seems to answer your question :)

Paul said...

Very helpful. Thank you

Karthick V said...

Nice article...
but this source code is not working for Android 2.2..
can u send source code for this version .. thank you Sir ,,,
Mail id- karthi.vels@gmail.com

Gabor Paller said...

karthick, the example program was written for pre-1.0 SDK. The key methods, however, are still the same. There should be no problem converting it to current Android SDK.

Anonymous said...

i downloaded source code, it is giving lot of error like SyncColumns cannot be resolved to a type,Implicit super constructor SQLiteOpenHelper() is undefined for default constructor. Must define an explicit constructor, please help me to run this project

Gabor Paller said...

Anonymous, I updated the post with a link to an updated example program.

Unknown said...

This information is impressive; I am inspired with your post writing style & how continuously you describe this topic. After reading your post, thanks for taking the time to discuss this, I feel happy about it and I love learning more about this topic.|Android Training in chennai with placement | Android Training in velachery