Android’s ContentProviderOperation: “withBackReference” explained

The class ContentProviderOperation.Builder has a method named withValueBackReference(String key, int previousResult). The API states ominously “Add a ContentValues back reference. A column value from the back references takes precedence over a value specified in withValues(ContentValues)”. Now, thank you, Google! That about explains everything!

Android’s documentation is never short of surprises. A lot of stuff is explained wonderfully but from time to time there are methods or members that sorely lack a decent documentation. This is an example of the latter. The sad thing is, that this method is very useful and understanding it makes using content providers that much easier and faster.

So let me shed some light on this method. If you are using a bunch of ContentProviderOperations chances are that you might want to use the id of an entry you inserted in a previous operation. That is where this method comes into play. Say you want to add a contact. You have to add a row to RawContacts and you have to add at least one row to the data table with additional information to your contact. Say we add an email, a birthday and a phone number.

The list of operations would look like this:

  1. Insertion of raw contact
  2. Insertion of email
  3. Insertion of birthday
  4. insertion of phone number

In this list every operation after the first one has to use the id of the raw contact to which the information should be added. But this id doesn’t exist prior to the insertion into the raw contacts table in the first operation. So you have to take the value of the first operation. But you don’t know this value when creating the list of ContentProviderOperation objects. That’s what this method is about. The first parameter states the column within the current operation for which you want to use a previous result. And the second parameter is the index of the previous operation of which you want to use a value. In our case it’s the id from the first operation. As usual with Java the index of the operations begins at zero.

A lot of description. Now let me show you some code:

ops = new ArrayList<ContentProviderOperation>();
ops.add(ContentProviderOperation.
         newInsert(RawContacts.CONTENT_URI)
   .withValue(RawContacts.ACCOUNT_TYPE, someAccountType)
   .withValue(RawContacts.ACCOUNT_NAME, someAccountName)
   .build());
ops.add(ContentProviderOperation.
         newInsert(ContactsContract.Data.CONTENT_URI)
   .withValueBackReference(Data.RAW_CONTACT_ID,  0)
   .withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE)
   .withValue(StructuredName.DISPLAY_NAME, displayName)
   .withValue(StructuredName.FAMILY_NAME, lastName)
   .withValue(StructuredName.GIVEN_NAME, firstName)
   .build());
ops.add(ContentProviderOperation.
         newInsert(ContactsContract.Data.CONTENT_URI)
   .withValueBackReference(Data.RAW_CONTACT_ID,  0)
   .withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE)
   .withValue(Phone.NUMBER, phoneNumber)
   .withValue(Phone.TYPE, Phone.TYPE_HOME)
   .withValue(Phone.LABEL, label)
   .build());
ops.add(ContentProviderOperation.
         newInsert(ContactsContract.Data.CONTENT_URI)
   .withValueBackReference(Data.RAW_CONTACT_ID, 0)
   .withValue(Data.MIMETYPE, Event.CONTENT_ITEM_TYPE)
   .withValue(Event.TYPE, Event.TYPE_BIRTHDAY)
   .withValue(Event.START_DATE, dateStr)
   .build());
try {
   getContentResolver().
         applyBatch(ContactsContract.AUTHORITY, ops);
} catch (RemoteException e) {
   // some error handling
} catch (OperationApplicationException e) {
   // some error handling
}

Now you might wonder: With inserting an element the return value is a URI but what you need within those other operations is the id as a long value. How does this work? Gladly Android does this for you as well. For URI values it uses ContentUris.parseId(Uri) to get the id. For all other values it takes an int – e.g. for the number of deleted records as a result of a delete operation.

You see, it’s actually quite simple. If only Android’s developers had it explained properly :-(

What other good examples of cryptic documentation are out there? Which would you like to get explained in more detail?

Share this article:

You can leave a response, or trackback from your own site.

14 Responses to “Android’s ContentProviderOperation: “withBackReference” explained”

  1. Hey Wolfram,

    I love your blog… thanks a lot for clarifying these things. I just began reading up on SyncAdapters two days ago and I also found that the documentation really lacks in those areas. That said, I have found that the way it all integrates with the Android system is quite magical. Thanks again!

    Alex

    • Wolfram Rittmeyer says:

      Alex, thanks! Could you please add some more hints as to what is missing? I might cover them in future posts.

      I also plan a post about sync adapters very soon – maybe in two weeks time. But probably too late for you.

      • Well, I managed to write the SyncAdapter and it appears to auto-update and perform syncs as it should.

        The main thing that I find lacking in the documentation is how to cover the edge cases (i.e. unauthorized HTTP requests, IOExceptions, etc.) and how to appropriately report these errors back to the SyncAdapter. Some of this stuff is hard to test (i.e. if the access token is revoked and an HTTP 401 response status is returned, I know that you should call “invalidateAuthToken”, but what comes after that?). This might be way too specific for a simple tutorial, so I might have to just go about figuring it out myself. It is somewhat annoying though that there isn’t an article on the developers site though…

        It won’t be too late… although my implementation is probably not 100% robust as of right now, I’ll probably spend more time on the UI and additional features until then.

        Thanks again!
        Alex

        • Thanks, Alex, for your answer.

          I guess I will only touch on these topics – if at all – in my initial, tutorial style post. But I always tend to surround these kind of posts with advanced ones. In these I will definitely cover more detailed questions about sync adapters and thus one or two of your edge cases.

  2. Rehab says:

    Hi Wolfram,
    Thanks for this useful post.
    I have a question about ‘withBackReference’, what if the uri for the current operation depends on the id returned by the previous operation?
    Thanks,
    Rehab

    • You cannot use withValueBackReference() for this. You have to construct the URI yourself using ContentUris.withAppendedId(). But you still should use ContentProviderOperation. See my post on how to use ContentProviderOperation.

      • Rehab says:

        Thanks for quick reply.
        I already read this post, but how to use it while the URI for the current operation depends on the previous operation result, i should got the id to construct the URI, but i didn’t find any way to get it with ContentProviderOperation.
        Do you have any suggestion?
        Thanks,
        Rehab

        • Sorry, Rehab. Didn’t get your question the first time. What I don’t understand is why you need the id to build a URI? You have different paths but use the same ids?

          • Rehab says:

            Yes, i have 4 operations(accessing 4 tables), 3 of them depends on the result of the first one (the URI for each contain the id of the first one), then this process repeated for more rows, so i need to make this with ContentProviderOperration to be faster.

  3. Rehab says:

    looked at the source code for the ContentProvider.applyBach(), it make a loop over the operations and call apply() for each operation separately and not in one transaction!!
    this is the link for this part in the source code: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.1.1_r1/android/content/ContentProvider.java#ContentProvider.applyBatch%28java.util.ArrayList%29

  4. Bashir says:

    Events are not getting inserted.

Leave a Reply

You can also subscribe without commenting.

Subscribe to RSS Feed My G+-Profile Follow me on Twitter!