What is cursor.setNotificationUri() used for?
If you call Cursor.setNotificationUri()
, Cursor will know what ContentProvider Uri it was created for.
CursorLoader
registers its own ForceLoadContentObserver
(which extends ContentObserver
) with the Context
's ContentResolver
for the URI you specified when calling setNotificationUri
.
So once that ContentResolver
knows that URI's content has been changed [ this happens when you call getContext().getContentResolver().notifyChange(uri, contentObserver);
inside ContentProvider
's insert()
, update()
and delete()
methods ] it notifies all the observers including CursorLoader's ForceLoadContentObserver
.
ForceLoadContentObserver
then marks Loader's mContentChanged as true
CursorLoader
registers observer for the cursor, not to the URI.
Look into CursorLoader's source code below. Notice that CursorLoader
registers contentObserver
to the cursor
.
/* Runs on a worker thread */ @Override public Cursor loadInBackground() { synchronized (this) { if (isLoadInBackgroundCanceled()) { throw new OperationCanceledException(); } mCancellationSignal = new CancellationSignal(); } try { Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection, mSelectionArgs, mSortOrder, mCancellationSignal); if (cursor != null) { try { // Ensure the cursor window is filled. cursor.getCount(); cursor.registerContentObserver(mObserver); } catch (RuntimeException ex) { cursor.close(); throw ex; } } return cursor; } finally { synchronized (this) { mCancellationSignal = null; } }
The Cursor
needs to call method setNotificationUri()
to register mSelfObserver
to the uri
.
//AbstractCursor.javapublic void setNotificationUri(ContentResolver cr, Uri notifyUri, int userHandle) { synchronized (mSelfObserverLock) { mNotifyUri = notifyUri; mContentResolver = cr; if (mSelfObserver != null) { mContentResolver.unregisterContentObserver(mSelfObserver); } mSelfObserver = new SelfContentObserver(this); mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver, userHandle); // register observer to the uri mSelfObserverRegistered = true; } }
Inside the contentProvider
's insert
, update
, delete
methods, you need to call getContext().getContentResolver().notifyChange(uri, null);
to notify change to the uri
observers.
So if you don't call cursor#setNotificationUri()
, your CursorLoader
will not receive notification if data underlying that uri
changes.
I use one URI for the cursor adaptor.
@Overridepublic void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Bundle args = new Bundle(); Uri uri = TemperatureContract.SensorEntry.buildSensorID0AddressUri(mDeviceAddress); args.putParcelable("URI", uri); getSupportLoaderManager().initLoader(0, args, this);}@Overridepublic Loader<Cursor> onCreateLoader(int id, Bundle args) { if (args != null) { Uri mUri = args.getParcelable("URI"); return new CursorLoader(this, mUri, null, // projection null, // selection null, // selectionArgs null); // sortOrder } else { return null; }}
On another class, I use a different URI to change the database contents. To have my view updated, I had to change the default implementation of the data provider's update
method. The default implementation only notifies the same URI. I have to notify another URI.
I ended up by calling the notifyChange()
twice on my data provider class, on the update
method:
@Overridepublic int update( Uri uri, ContentValues values, String selection, String[] selectionArgs) { final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); final int match = sUriMatcher.match(uri); int rowsUpdated; switch (match) { case ...: break; case SENSOR_BY_ID_AND_ADDRESS: String sensorId = TemperatureContract.SensorEntry.getSensorIdFromUri(uri); String sensorAddress = TemperatureContract.SensorEntry.getSensorAddressFromUri(uri); rowsUpdated = db.update( TemperatureContract.SensorEntry.TABLE_NAME, values, "sensorid = ? AND address = ?", new String[]{sensorId, sensorAddress}); if (rowsUpdated != 0) { Uri otheruri = TemperatureContract.SensorEntry.buildSensorID0AddressUri(sensorAddress); getContext().getContentResolver().notifyChange(otheruri, null); } break; case ...: break; default: throw new UnsupportedOperationException("Unknown uri: " + uri); } if (rowsUpdated != 0) { getContext().getContentResolver().notifyChange(uri, null); } return rowsUpdated;
I did the same for the insert
and delete
methods.