How do CursorLoader automatically updates the view even if the app is inactive?
I have been working on a small To-Do list app. I used CursorLoader to update the ToDolistview from a content provider. I have a written a function onNewItemAdded()
, which is called when user enters a new item in the text view and clicks enter. Refer below:
public void onNewItemAdded(String newItem) {
ContentResolver cr = getContentResolver();
ContentValues values = new ContentValues();
values.put(ToDoContentProvider.KEY_TASK, newItem);
cr.insert(ToDoContentProvider.CONTENT_URI, values);
// getLoaderManager().restartLoader(0, null, this); // commented for the sake of testing
}
@Override
protected void onResume() {
super.onResume();
//getLoaderManager().restartLoader(0, null, this); // commented for the sake of testing
}
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
CursorLoader loader = new CursorLoader(this,
ToDoContentProvider.CONTENT_URI, null, null, null, null);
Log.e("GOPAL", "In the onCreateLoader");
return loader;
}
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
int keyTaskIndex = cursor.getColumnIndexOrThrow(ToDoContentProvider.KEY_TASK);
Log.e("GOPAL", "In the onLoadFinished");
todoItems.clear();
if (cursor.moveToNext() == false) Log.e("GOPAL", "Empty Cursor");
else {
while (cursor.moveToNext()) {
ToDoItem newItem = new ToDoItem(cursor.getString(keyTaskIndex));
todoItems.add(newItem);
}
aa.notifyDataSetChanged(); // aa is arrayadapter used for the listview
}
}
I have read, CursorLoader automatically updates the view, whenever there is a data change in the content provider db. That means I suppose, getLoaderManager().restartLoader(0, null, this)
has to be called implicitly whenever there is a change in data, right?
But that is not happening. Whenever I add a new item (the item is added to the db from onNewItemAdded
, but restartLoader is not explicitly called), pause this activity and resume it back. I don't see any implicit call to restartLoader(even though db is changed) and the listview also is not updated with new item added. Why is that? How does a CursorLoader
automatically updates the view even if app is not active???
Thanks :)
EDIT: I have also used getContext().getContentResolver().notifyChange(insertedId, null)
in insert of my content provider.
Solution 1:
I found the answer for my question. In general, CursorLoader doesn't automatically detect data changes and load them to view. We need to track URI for changes. This can be done by following steps:
Registering an Observer in content resolver through cursor using: (Done in the query method of ContentProvider)
cursor.setNotificationUri(getContext().getContentResolver(), uri);
-
Now when there is any change in URI underlying data using
insert()
/delete()
/update(),
we notify theContentResolver
about the change using:getContext().getContentResolver().notifyChange(insertedId, null);
This is received by the observer, we registered in step-1 and this calls to
ContentResolver.query()
, which inturn callsContentProvider
'squery()
method to return a fresh cursor toLoaderManager
.LoaderManager
callsonLoadFinished()
passing this cursor, along with theCursorLoader
where we update the View (usingAdapter.swapCursor()
) with fresh data.
For Custom AsyncTaskLoaders:
At times we need our custom loader instead of CursorLoader. Here we can use someother object other than cursor to point to the loaded data (like list etc). In this we won't be having previlige to notify ContentResolver through cursor. The application may also not have a content Provider, to track URI changes. In this scenario we use BroadcastReceiver or explicit ContentObserver to achieve automatic view updation. This is as follows:
- We need to define our custom loader which extends
AsyncTaskLoader
and implements all its abstract methods. UnlikeCursorLoader
, our Custom Loader may or may not use a content Provider and it's constructor may not call toContentResolver.query()
, when this loader is instatiated. So we use a broadcast receiver to serve the purpose. - We need to instantiate a
BroadCastReceiver
orContentObserver
inOnStartLoading()
method of abstractAsyncTaskLoader
class. - This BroadCast receiver should be defined to receive data-changing broadcasts from content provider or any system events(Like new app installed) and it has to call loader's
onContentChanged()
method, to notify the loader about the data change. Loader automatically does the rest to load the updated data and callonLoadFinished()
to update the view.
For more details refer this: http://developer.android.com/reference/android/content/AsyncTaskLoader.html
I found this very useful for clear explanation : http://www.androiddesignpatterns.com/2012/08/implementing-loaders.html
Solution 2:
Well, I think you can restart the loader on certain events. E.g. in my case I have an activity of TODOs. On clicking 'add' option, it launches new activity which has view to feed new TODO.
I am using following code in parent activity's onActivityResult()
getLoaderManager().restartLoader(0, null, this);
It works fine for me. Please share if there is any better approach.
Solution 3:
get a reference to your loader while initializing as follows
Loader dayWeatherLoader = getLoaderManager().initLoader(LOADER_DAY_WEATHER, null, this);
then create a class that extends ContentObserver as follows
class DataObserver extends ContentObserver {
public DataObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange, Uri uri) {
dayWeatherLoader.forceLoad();
}
}
Then register content observer inside onResume lifecycle method as follows
@Override
public void onResume() {
super.onResume();
getContext().getContentResolver().registerContentObserver(CONTENTPROVIDERURI,true,new DayWeatherDataObserver(new Handler()));
}
Whenever there is a change in the underlying data of content provider, the onChange method of contentobserver will be called where you can ask loader to load the data again