androidandroid-contentproviderandroid-contactsrawcontactid

How to add city field to a contact in Android using ContentProviderOperation


I'm creating a contacts app that will have all the basic features of a Contacts app (and some extra features of course). While implementing the basic features, I'm stuck at a place:

I'm having an activity in which the user can change the city name of a contact. If the user is already having a city, I can update it using the following code:

ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
String contactId = id; // got it from ContactsContract.Contacts._ID
String mimeType = ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE;
String selection = ContactsContract.Data.CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ? AND " + ContactsContract.CommonDataKinds.StructuredPostal.TYPE + " = ?";
String[] values = new String[]{contactId, mimeType, String.valueOf(ContactsContract.CommonDataKinds.StructuredPostal.TYPE_HOME)};
ops.add(
 android.content.ContentProviderOperation.newUpdate(
  android.provider.ContactsContract.Data.CONTENT_URI)
   .withSelection(selection, values)
   .withValue(ContactsContract.CommonDataKinds.StructuredPostal.CITY, "California")
   .build()
);
contentResolver.applyBatch(ContactsContract.AUTHORITY, ops);

But, the above code is not working for a contact that doesn't have any details other than phone number. After browsing a lot, I've found the following way to do it:

ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
String rawContactId = id;
String mimeType = ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE;
String[] values = new String[]{contactId, mimeType, String.valueOf(ContactsContract.CommonDataKinds.StructuredPostal.TYPE_HOME)};
ops.add(
 android.content.ContentProviderOperation.newInsert(
  android.provider.ContactsContract.Data.CONTENT_URI)
   .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactId)
   .withValue(ContactsContract.Data.MIMETYPE, mimeType)
   .withValue(ContactsContract.CommonDataKinds.StructuredPostal.CITY, "California")
   .build()
);
contentResolver.applyBatch(ContactsContract.AUTHORITY, ops);

In the above case, I'm getting the rawContactId from ContactsContract.RawContacts.CONTENT_URI with ContactsContract.Data.CONTACT_ID equal to normal contactId. I was getting different rawContactId for different accounts - Google, WhatsApp, Skype, etc. I tried updating with all the rawContactIds, but still it was not getting updated. Can anybody please help me how to fix it?


Solution

  • You didn't specify what exactly is not working when you apply to above code, but I can see a few issues in it.

    First you need to have a clear understanding of how contact data is stored in the database:

    1. Contacts - each entry represents one contact, and groups together one or more RawContacts
    2. RawContacts - each entry represents data about a contact that was synced in by some SyncAdapter (e.g. Whatsapp, Google, Facebook, Viber), this groups multiple Data entries
    3. Data - The actual data about a contact, emails, phones, etc. each line is a single piece of data that belongs to a single RawContact

    ISSUE 1

    So if you're trying to update an existing postal-address, you should be careful not to use contactId as your key, because a single contact (referenced by a contactId) may have multiple postal-addresses in multiple raw-contacts each with multiple postal-address data rows. your newUpdate call might then update the city in ALL addresses.

    So if you have a contact "David" that has addresses:

    and your user is now trying to update "Paris" to "Lyon", your code might update ALL 3 addresses to Lyon.

    Your key then must be the current Data._ID of the specific Data row you're trying to update.

    ISSUE 2

    If you're trying to insert a new data row to an existing raw-contact, for example a completely new postal-address, you'll need to specify the specific RawContact ID you're trying to insert into, and not use withValueBackReference - that's only useful when you're now creating a whole new RawContact, and don't know what will be the RawContact ID it'll get yet, so you're doing a back-reference to the ID your new RawContact will get from a previous call to newInsert of a RawContact row.

    Also, in this case just the CITY value is not enough, as you'll get a whole postal-address comprised of CITY only, like this:

    What you want to do here is collect all the postal-address values, and add them all into a single new Data row, like so:

    ops.add(
     ContentProviderOperation.newInsert(Data.CONTENT_URI)
       .withValue(Data.RAW_CONTACT_ID, rawContactId)
       .withValue(Data.MIMETYPE, mimeType)
       .withValue(StructuredPostal.STREET, "123 Lane")
       .withValue(StructuredPostal.CITY, "Los Angles")
       .withValue(StructuredPostal.REGION, "California")
       .withValue(StructuredPostal.COUNTRY, "United States")
       .build()
    );
    

    ISSUE 3

    If you're trying to insert just the CITY value to an existing postal-address row, you'll need to do an update, not an insert, into the specific Data ID, something like this:

    String selection = Data._ID + " = ?";
    ops.add(
     ContentProviderOperation.newUpdate(Data.CONTENT_URI)
       .withSelection(selection, new String[]{ dataId })
       .withValue(StructuredPostal.CITY, "Los Angeles")
       .build()
    );