How to implement a ContentObserver in a fragment using CursorAdapter and LoaderCallbacks?
android, android-contentprovider, android-cursoradapter, android-cursorloader, contentobserver
Solution
`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 ...
Problem
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: - Is it okay to pass `null` to `notifyChange()` as the observer parameter? If not, what object am I supposed to pass here? - Should I pass the `uri` or the `itemUri` in `notifyChange()`? Why? - What method do I have to call in `CommentsObserver#onChange()` to update the list of comments? - Is there no interface I could implement with the `ListFragment` instead of the inner class instance of `ContentObserver`? - I simply instantiate a `new Handler()` in the constructor of `CommentsObserver`. This seems not correct to me - please explain.