42

I cannot figure out the exact semantics of withValueBackReference.

I've read the example code (for example the code which adds a new contact) using this method, providing a backReference value of 0. What does this mean?

The documentation says:

A column value from the back references takes precedence over a value specified in withValues(ContentValues)

Jonathan Soifer
  • 2,715
  • 6
  • 27
  • 50
curioustechizen
  • 10,572
  • 10
  • 61
  • 110
  • I found this discussion on SO - it talks about using the withValueBackReference method in a case where the objective is to save *both the master and details records* in a single operation. However, I still dont understand how the back reference value of 0 figures here! http://stackoverflow.com/questions/3224857/master-detail-using-contentresolver-applybatch – curioustechizen Jan 12 '11 at 11:23

1 Answers1

169

This question relates to batch operation on a content provider. The example is modified from this related question.

When creating a batch of operations first create a list of operations to perform using:

ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();

then apply them to the content provider using the applyBatch method.

ContentProviderResult[] results = this.getContentResolver().applyBatch(FooBar.AUTHORITY, operations);

That is the basic concept so let's apply it. Suppose we have a content provider which handles uris for Foo records and some child records called Bar.

content://com.stackoverflow.foobar/foo

content://com.stackoverflow.foobar/foo/#/bar

For now we'll just insert 2 new Foo recordscalled "Foo A" and "Foo B", here's the example.

ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();

//add a new ContentProviderOperation - inserting a FOO record with a name and a decscription
operations.add(ContentProviderOperation.newInsert(intent.getData())
    .withValue(FOO.NAME, "Foo A")
    .withValue(FOO.DESCRIPTION, "A foo of impeccable nature")
    .build());

//let's add another
operations.add(ContentProviderOperation.newInsert(intent.getData())
    .withValue(FOO.NAME, "Foo B")
    .withValue(FOO.DESCRIPTION, "A foo of despicable nature")
    .build());

ContentProviderResult[] results = this.getContentResolver().applyBatch(FooBar.AUTHORITY, operations);

Nothing special here, we are adding 2 ContentProviderOperation items to our list and then applying the list to our content provider. The results array filled with the id's of the new records that we have just inserted.

So say we wanted to do something similar but we also want to add some child records into our content provider in one batch operation. We want to attach the child records to the Foo records we just created. The problem is we don't know the id of the parent Foo records because the batch has not been run. This is where the withValueBackReference helps us. Let's see an example:

ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();

//add a new ContentProviderOperation - inserting a Foo record with a name and a decscription
operations.add(ContentProviderOperation.newInsert(intent.getData())
    .withValue(FOO.NAME, "Foo A")
    .withValue(FOO.DESCRIPTION, "Foo of impeccable nature")
    .build());

//let's add another
operations.add(ContentProviderOperation.newInsert(intent.getData())
    .withValue(FOO.NAME, "Foo B")
    .withValue(FOO.DESCRIPTION, "Foo of despicable nature")
    .build());

//now add a Bar record called [Barbarella] and relate it to [Foo A]
operations.add(ContentProviderOperation.newInsert(intent.getData()
    .buildUpon()
    .appendPath("#") /* We don't know this yet */
    .appendPath("bar")
    .build())
.withValueBackReference (BAR.FOO_ID, 0) /* Index is 0 because Foo A is the first operation in the array*/
.withValue(BAR.NAME, "Barbarella")
.withValue(BAR.GENDER, "female")
.build());

//add a Bar record called [Barbarian] and relate it to [Foo B]
operations.add(ContentProviderOperation.newInsert(intent.getData()
    .buildUpon()
    .appendPath("#") /* We don't know this yet */
    .appendPath("bar")
    .build())
.withValueBackReference (BAR.FOO_ID, 1) /* Index of parent Foo B is 1*/
.withValue(BAR.NAME, "Barbarian")
.withValue(BAR.GENDER, "male")
.build());

ContentProviderResult[] results = this.getContentResolver().applyBatch(FooBar.AUTHORITY, operations);

So the withValueBackReference() method allows us to insert the related records before we know the id of the parent we want to relate them to. The back reference index is simply the index of the operation that will return the id we want to look for. It is perhaps easier to think of it in terms of which result you would expect to contain the id. e.g results[1] would contain the id for "Foo B" so the index we use to back reference to "Foo B" is 1.

Community
  • 1
  • 1
Moog
  • 10,193
  • 2
  • 40
  • 66
  • 5
    That was an impeccable explanation. Thanks a ton! How i wish I could upvote more than once. – curioustechizen Jul 19 '11 at 01:50
  • 3
    This is gold, one of the only decent explanations of backReferences I've seen. Got it implemented in my app - thanks – David Snabel-Caunt Oct 26 '11 at 13:46
  • Best answer ever, thank you so much, it will be good to have a star flag that you can only put in 5 responses :) You definitely own one! – Goofyahead May 21 '12 at 16:04
  • Thanks alot. Cleared up some confusion:) – DanielGrech Jul 19 '12 at 07:04
  • This is a great answer. I think it would make it even more clear to add a sentence or to explaining what "intent.getData()" actually points to in your example. If I am reading the code correctly this is the content URI (operation type) and in your example could be interchanged with BAR.FOO_ID. That way, the system allows you to specify the "Nth result to the operation of type BAR.FOO_ID". Am I right about that? – GolfARama Jan 06 '13 at 18:56
  • In this example `intent.getData()` is equivalent to `Uri.parse("content://com.stackoverflow.foobar/foo")`. – Moog Jan 16 '13 at 21:26
  • 5
    Are you sure the replacement of the `#` sign in `content://com.stackoverflow.foobar/foo/#/bar` with the id of the master record actually works? I tried it and it seems that it does not. If I look into the [source of ContentProviderOperation](http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.1.2_r1/android/content/ContentProviderOperation.java#ContentProviderOperation.apply%28android.content.ContentProvider,android.content.ContentProviderResult%5B%5D,int%29) it seems that the backreferences are only applied to the `ContentValues`, not the URI. – aha Mar 07 '13 at 09:43
  • Merlin, I have the same question as @aha. I haven't had the chance to take a very deep look at the code he pointed to, so I can't tell if that particular implementation has this issue or not. In any case, I'm more concerned about the contract. What is the contract here? – batbrat Mar 22 '14 at 14:41
  • 1
    Excellent explanation and made me aware of a feature that has saved me many hours of pain. With thanks. – Mark OB Nov 01 '14 at 11:41
  • Thank you man, you save my day...., even android documentation lack of these api details. – Sanath Feb 23 '17 at 08:15
  • This will be working only in case of master-details relation? I've tried to do the same but without master-detail structure, the problem is the `withValueBackReference` gives back the first table's name.. – cylon Jul 25 '17 at 13:44
  • @cylon that is the default use case, but you might be able to find more imaginative uses for it. – Moog Aug 03 '17 at 18:43
  • What about in situation with update where you are supposed to already have the RAW_CONTACT_ID, what does the second parameter relate to then? – NVA Mar 11 '18 at 08:35
  • If you already know the parent ID then you just use `withValue(...)` instead of `withValueBackReference(..., index)` so there is no second parameter. – Moog Jun 25 '19 at 08:21