Search code examples
androidandroid-contentproviderandroid-loadermanagerandroid-cursorloader

CursorLoader does not refresh if notifyChange() is called before CursorLoader registers ContentObserver


I have a CursorLoader that loads data from local SQLite database.

The data in SQLite is actually a cached copy of a remote API response, so I start an asynchronous refresh of SQLite data whenever it is queried by the CursorLoader.

Following is the sequence of steps I follow to respond to the data query in my ContentProvider:

  1. Query SQLite for cached data. cursor.setNotificationUri(getContext().getContentResolver(), uri) is called on the returned cursor.

  2. Start an asynchronous call to remote API. Once the response is received, getContext().getContentResolver().notifyChange(uri, null) is called.

  3. Sleep for 5 seconds to cause a delay in receipt of cursor by the CursorLoader. (This is done to easily reproduce my problem).

  4. Return the cursor obtained in Step 1 to the CursorLoader.

In the less likely but possible scenario of the asynchronous remote API call completing (and notifyChange() being called) before the CursorLoader could register a ContentObserver on the obtained cursor by calling registerContentObserver(cursor, mObserver) [ see http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/content/CursorLoader.java#CursorLoader.loadInBackground%28%29 ], the data change notification is missed by the CursorLoader and therefore the Activity does not see updated data.

I can get around this problem by writing my LoaderCallBacks such that the data is loaded twice - first only cached data is loaded and then a refresh request is made. This way the CursorLoader gets to register ContentObserver before the refresh call starts.

However, this is something that should ideally be handled by the CursorLoader itself, as CursorLoader advertises automatic update of the cursor. Doing it in every Activity causes a lot of bloat in Activity code.


Solution

  • My approach is to register the observer before starting with the loading of the cursor. You need to register for the Uri instead of for the cursor.

     getActivity().getContentResolver().registerContentObserver(
     your_Uri, true,
     new YourObserver(mHandler));
    
    getLoaderManager().initLoader(0, null, this);