How to implement a ContentObserver in a fragment using CursorAdapter and LoaderCallbacks?

JJD picture JJD · Aug 16, 2012 · Viewed 8.5k times · Source

I am using a CursorAdapter in a ListFragment to load and display a list of comments.

public class CommentsFragment extends ListFragment implements LoaderCallbacks<Cursor> {

    protected Activity mActivity;
    protected CursorAdapter mAdapter;

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        mActivity = getActivity();
        mAdapter = new CommentsCursorAdapter(mActivity, null, 0);
        setListAdapter(mAdapter);
        mActivity.getContentResolver().registerContentObserver(CustomContract.Comments.CONTENT_URI, false, new CommentsObserver());
        getLoaderManager().initLoader(0, null, this);
    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle extras) {
        Uri uri = CustomContract.Comments.CONTENT_URI;
        return new CursorLoader(mActivity, uri, null, null, null, null);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        mAdapter.swapCursor(cursor);
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        mAdapter.swapCursor(null);
    }

    protected class CommentsObserver extends ContentObserver {

        public CommentsObserver() {
            super(new Handler());
        }

        @Override
        public void onChange(boolean selfChange) {
            super.onChange(selfChange);
            // TODO Trigger a reload.
        }

    }
}

In the associated ContentProvider I added notifyChange() for the insert action.

@Override
public Uri insert(Uri uri, ContentValues values) {
    final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    int match = sUriMatcher.match(uri);
    switch (match) {
        case COMMENTS: {
            long id = db.insert(DatabaseProperties.TABLE_NAME_COMMENTS, null, values);
            Uri itemUri = ContentUris.withAppendedId(uri, id);
            // FIXME Which one is right?
            getContext().getContentResolver().notifyChange(itemUri, null);
            getContext().getContentResolver().notifyChange(uri, null);
            return itemUri;
        }
        default: {
            throw new UnsupportedOperationException("Unknown URI: " + uri);
        }
    }
}

Questions:

  1. Is it okay to pass null to notifyChange() as the observer parameter? If not, what object am I supposed to pass here?
  2. Should I pass the uri or the itemUri in notifyChange()? Why?
  3. What method do I have to call in CommentsObserver#onChange() to update the list of comments?
  4. Is there no interface I could implement with the ListFragment instead of the inner class instance of ContentObserver?
  5. I simply instantiate a new Handler() in the constructor of CommentsObserver. This seems not correct to me - please explain.

Answer

Selvin picture Selvin · Aug 16, 2012

SqlCursor already have internal ContentObserver and since you're using this Cursor implementation there is no need for own ContentObserver implementation.

For easy use you can add:

class MyContentProvider extends ContentProvider{
  //rest implementation goes here
  @Override
  public Cursor query(Uri uri, String[] projection, String selection,
            String[] selectionArgs, String sortOrder) {
    Cursor cursor = null;
    //check uri, select table name based on uri, etc ...
    cursor = db.query(/*.....*/);
    //add this line:
    cursor.setNotificationUri(getContext().getContentResolver(), uri);
    //before you return cursor
    return cursor;
  }
  @Override
  public Uri insert(Uri uri, ContentValues values) {
     /* ... */
       //you can notify only "dir" uri and is should be enough
       getContext().getContentResolver().notifyChange(uri, null);
       return itemUri;
     /* ... */
  }
  //rest of implementation goes here
}

after this, CursorLoader internal implementation will take care of reloading/refreshing data ...