28

I've just implemented a CursorLoader and it works great! In fact, I didn't believe that my ListView would automatically update when the underlying data changed until I tested it. This apparently is the magic of setNotificationUri.

My question is, how does it know when the data in the cursor has changed? Say I quietly insert an additional row somewhere. Does the underlying mechanism constantly query the database and compare it with the past data? Won't that be horribly inefficient if the datasets are large?

Before I used cursorloaders, I would manually refresh when necessary. It's great that I don't have to do this anymore, but is it efficient to let the CursorLoader to this in the background?

General Grievance
  • 4,555
  • 31
  • 31
  • 45
Bhagwad Jal Park
  • 1,093
  • 1
  • 13
  • 22

1 Answers1

38

Please, correct me if I'm wrong somewhere.

ContentProvider calls something like this in query(…) method:

// Tell the cursor what uri to watch, so it knows when its source data changes
cursor.setNotificationUri(getContext().getContentResolver(), uri);

CursorLoader get cursor back and registers an observer.

/* Runs on a worker thread */
@Override
public Cursor loadInBackground() {
    Cursor cursor = getContext().getContentResolver().query(mUri, mProjection,
            mSelection, mSelectionArgs, mSortOrder);
    if (cursor != null) {
        // Ensure the cursor window is filled
        cursor.getCount();
        registerContentObserver(cursor, mObserver);
    }
    return cursor;
}

/**
 * Registers an observer to get notifications from the content provider
 * when the cursor needs to be refreshed.
 */
void registerContentObserver(Cursor cursor, ContentObserver observer) {
    cursor.registerContentObserver(mObserver);
}

When someone modifies data, ContentProvider notifies ContentResolver about changes:

getContext().getContentResolver().notifyChange(uri, null);

ContentResolver in its turn notifies all registered observers.

Observer, registered by CursorLoader, forces it to load new data.

StenaviN
  • 3,687
  • 24
  • 34
  • 1
    Hmm...I don't write any of the above code in Cursorloader. I just create a new cursorloader in "onCreateLoader" and swap in the new data in "onLoadFinished" like here: http://developer.android.com/guide/components/loaders.html But I think you're right. The "notifyChange" statement tells everyone that something's changed and the system automagically knows whether it relates to any given uri or cursor. Pretty neat. – Bhagwad Jal Park Aug 03 '12 at 21:10
  • 1
    would CursorLoader be restarted even if I update db through SQLiteDatabase instead of ContentProvider? – AndroidGecko Dec 15 '12 at 11:38
  • 11
    @MarcinCzech: "would CursorLoader be restarted even if I update db through SQLiteDatabase instead of ContentProvider?" -- no. You have to do all updates through the `ContentProvider`, and the `ContentProvider` has to make the appropriate calls to tie into the observer framework. – CommonsWare Dec 18 '12 at 15:13
  • From reading documentation, my understanding is that `cursor.setNotificationUri(getContext().getContentResolver(), uri)` will lead to any listeners on the given ContentResolver being notified when anything under the given uri is notifyChange()d. However, there aren't any listeners on the resolver, since it is fresh from the context. Are you saying that as an invisible side effect it _also_ updates some internal state in the cursor? – Dave Cameron Jan 02 '14 at 22:09
  • Strange, in IoSchedule app Google don't call setNotificationUri() in the query() of ScheduleProvider. But I added it in my provider's query() and now the data is reloading in all my cursor list fragments, not just the current one. – WindRider May 09 '14 at 12:09
  • @CommonsWare And what is the "appropriate calls to tie into the observer framework"? – f470071 Sep 23 '15 at 05:42
  • @f470071: `notifyChange()`, mostly. – CommonsWare Sep 23 '15 at 11:18
  • @commonsWare Well in my case it doesn't cut. I have two activities, the first one dispalys data in listview, the second one changes data and calls notifyChange() and the changes are not reflected in first activity upon returning. Thinking about posting a separate question. – f470071 Sep 23 '15 at 12:16
  • 1
    @f470071: "the second one changes data and calls notifyChange()" -- I didn't say that an *activity* calls `notifyChange()`. I said the `ContentProvider` calls `notifyChange()`. The second activity should be working with the `ContentProvider` to change the data in the `ContentProvider`, and the `ContentProvider` is the one that calls `notifyChange()`. – CommonsWare Sep 23 '15 at 12:30
  • @CommonsWare Sorry, I was not clear enough. Of course the second activity uses content provider which calls notifyChange() on update, insert, delete. I will post separate question not to stray away in this thread. – f470071 Sep 23 '15 at 12:38
  • @CommonsWare Could you please take a look at http://stackoverflow.com/questions/32741634/how-to-make-notifychange-work-between-two-activities?noredirect=1#comment53329930_32741634. I am having trouble to make notifyChanges() work across activities. – f470071 Sep 23 '15 at 16:23