8

I'm looking for a way to suspend notifications on a given ContentProvider's Uri. The use case is:

  1. An Activity is bound to a CursorAdapter through a CursorLoader.
  2. A Service may do a lot of batch, single-row updates on a ContentProvider.
  3. The CursorLoader will reload its content on every row update, as the ContentProvider notifies listeners by ContentResolver#notifyChange.

Since I cannot edit the ContentProvider, and I have no control over the batch queries execution, is there a way to suspend notifications on a Uri (in the executing Service) until all of the ContentProvider-managed queries have been executed? I need this in order to avoid the flickering caused by the continuous requerying of the CursorLoader.

frapontillo
  • 10,499
  • 11
  • 43
  • 54
  • 1
    To get this right: You have neither influence over the Service nor the CP? If on the other hand the Service is in your control, please provide a bit more detail about which kind of CP updates you're doing. – Wolfram Rittmeyer Apr 08 '13 at 12:24
  • I am executing a series of "UPDATE or INSERT" kind of queries inside a simple `for` loop. I'm using a `ContentProvider` that notifies after every `insert` or `update` but, in this particular situation, I want to be able to suspend all notifications until I've finished updating all of the entities. – frapontillo Apr 08 '13 at 15:59
  • To be even more detailed, I am generating my `ContentProvider` with a generator by Nicolas Klein (https://github.com/foxykeep/ContentProviderCodeGenerator) that, at the end of an `update(Uri uri, ContentValues values, String selection, String[] selectionArgs)` does a simple `getContext().getContentResolver().notifyChange(uri, null);`. – frapontillo Apr 08 '13 at 16:12

3 Answers3

3

You cannot disable this mechanism in your Service. But you should try to batch them by using ContentProviderOperations.

I've written an introductory post about ContentProviderOperations and two additional posts covering the methods withYieldAllowed() and withBackReference() respectively.

Especially the latter one should be of interest for what you've described here.

With ContentProviderOperations you can batch multiple updates and inserts. If you then call applyBatch() on your ContentResolver object the ContentProvider executes them all at once.

Now I've never used Nicolas Klein's generator but since he is a very, very proficient Android developer and works at Google, I bet that the generated code makes use of transactions and calls notifyChange() only once for the complete batch at the end.

Exactly what you need.

Wolfram Rittmeyer
  • 2,402
  • 1
  • 18
  • 21
  • The piece of code we're talking about is [this one](https://github.com/foxykeep/ContentProviderCodeGenerator/blob/master/generator/res/provider.txt#L184), and I think Nicolas didn't put any notification in there because there's no table-level `Uri` to be notified, as any given `Operation` may work on any `Uri`. Am I correct? – frapontillo Apr 08 '13 at 22:27
  • 1
    You can use a generic URI. That's what ContactsContract and CalendarContract are doing. They notify you at the end of applyBatch using [CalendarContract.CONTENT_URI](http://developer.android.com/reference/android/provider/CalendarContract.html#CONTENT_URI) or [ContactsContract.AUTHORITY_URI](http://developer.android.com/reference/android/provider/ContactsContract.html#AUTHORITY_URI) respectively. I suggest to add something like this to the generated code. – Wolfram Rittmeyer Apr 09 '13 at 06:53
  • I could easily notify changes outside of the `ContentProvider` if I'm able to apply a batch of operations that simulate an `INSERT OR UPDATE`. Do you think that extending `ContentProviderOperation` and building a custom `InsertOrUpdate` operation is doable? That would solve it without messing with the generated code. – frapontillo Apr 09 '13 at 10:18
  • **UPDATE**: unfortunately, it wouldn't solve anything, as the `apply` method [simply calls](https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/content/ContentProviderOperation.java) `provider.insert`, `provider.update`, `provider.delete`, delegating the notification to the `ContentProvider` implementation of `insert`, `update` and `delete`. – frapontillo Apr 09 '13 at 10:28
  • The `apply()` method is only called if your `ContentProvider` doesn't override `applyBatch()`. But since Nicolas' generator overrides it `apply()` isn't called. Of course you could call `notifyChange()` on your `ContentRersolver` object with some meaningful URI directly from within your service. – Wolfram Rittmeyer Apr 09 '13 at 13:32
  • It looks to me that [`ContentProviderOperation#apply` is called](https://github.com/foxykeep/ContentProviderCodeGenerator/blob/master/generator/res/provider.txt#L192) from within the generated code. Then, that method will trigger a regular operation from the generated `ContentProvider`, leading to a notification. – frapontillo Apr 09 '13 at 15:06
  • 2
    In fact what CalendarContract and ContactsContract are doing is a bit more complicated. They store whether a batch is executing. If so, the do not notify within each of those insert-/update-/delete-methods. [See for example CalendarContract's method.](http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android-apps/4.2.2_r1/com/android/providers/calendar/SQLiteContentProvider.java#SQLiteContentProvider.insert%28android.net.Uri%2Candroid.content.ContentValues%29) But you are right, **apply is called for every ContentProvideroperation**. I was off the track. Sorry! – Wolfram Rittmeyer Apr 09 '13 at 19:07
  • Don't worry, I'm simply trying to figure out what the best approach would be, in order to test it and maybe submit a pull request with the desired behavior. The `CalendarContract` and `ContactsContract` implementations by Google make sense and are compatible with what I'm trying to do. All I have left to do would be an implementation of a custom `InsertOrReplace` `ContentProviderOperation`, right? – frapontillo Apr 09 '13 at 19:42
0

Can you substitute your own ContentResolver?

You may try extends ContentResolver with your own class then and you will may override method notifyChange and realize your logic there.

QArea
  • 4,955
  • 1
  • 12
  • 22
  • The thing is I have no information about the executed operation in `notifyChange`, as it may be a regular one that will need to be notified or a "batch" one that won't. – frapontillo Apr 09 '13 at 10:33
0

In your Content provider class, inside query() method before returning the cursor, just comment the code which looks something like this

cursor.setNotificationUri(getContext().getContentResolver(), uri);
1HaKr
  • 1,098
  • 1
  • 11
  • 18