1

I know on some phones contact ids change even on reboot, but I can't reproduce this on my own phone. But I can reproduce following case and would like to know if this case is solveable at least:

  • install whatsapp - it will add it's contacts to the ContentProvider
  • read contacts from ContactProvider => I get data set 1
  • delete whatsapp, reinstall it - it will add it's contacts to the ContentProvider again
  • read contacts from ContentProvider => I get data set 2

Result:

Between data set 1 and data set 2 I don't see any consistent data.

E.g.:

ContactsContract.Data.CONTACT_ID => changes, e.g. from 598 to 679 ContactsContract.Data.LOOKUP_KEY => changes as well, e.g. 3514i2b4948808eec75c9.3789r593-2D374B39.2797r594-2D374B39 to 3514i2b4948808eec75c9.2797r603-2D374B39.3789r670-2D374B39 * of course, some raw ids stay the same, but no common id for a full contact stays the same as far as I can see

What I want

I need an identifier for a contact that I can persistent in my app's database and that will also work after a change like the one described above. I need an identifier for a contact (not a raw contact), raw contacts may change of course during the lifetime of a contact (like in the example I describe above)...

Any ideas how I can solve this problem?

Code

For the sake of completeness, here's how I query the data from the ContentProvider, at least the cursor:

 private fun getCursor(offset: Int?, count: Int?): Cursor? {
    val selection = arrayOf(
            ContactsContract.Data.RAW_CONTACT_ID,
            ContactsContract.Data.CONTACT_ID,
            ContactsContract.Data.LOOKUP_KEY,
            ContactsContract.Data.PHOTO_ID,
            ContactsContract.Data.DISPLAY_NAME,
            ContactsContract.Data.ACCOUNT_TYPE_AND_DATA_SET,
            ContactsContract.CommonDataKinds.Email.DATA,
            ContactsContract.CommonDataKinds.Phone.NUMBER,
            ContactsContract.CommonDataKinds.Phone.TYPE,
            ContactsContract.CommonDataKinds.Phone.LABEL,
            ContactsContract.Contacts.Data.MIMETYPE,
            ContactsContract.RawContacts.ACCOUNT_NAME,
            ContactsContract.CommonDataKinds.Phone.IS_PRIMARY,
            ContactsContract.Data.CONTACT_LAST_UPDATED_TIMESTAMP,
            ContactsContract.RawContacts.SOURCE_ID
    )

    return AppProvider.get().context.contentResolver.query(
            ContactsContract.Data.CONTENT_URI,
            selection, null, null,
            ContactsContract.Data.CONTACT_ID + " ASC" + if (offset != null && count != null) " limit $count offset $offset" else "")
}
prom85
  • 16,896
  • 17
  • 122
  • 242

1 Answers1

0

LOOKUP_KEY is your friend.

LOOKUP_KEY

An opaque value that contains hints on how to find the contact if its row id changed as a result of a sync or aggregation.

So it's not a stable ID on its own, but it helps the ContactsContract DB to figure out the new contactID in case it changed.

You should use a pair of <CONTACT_ID, LOOKUP_KEY> to keep track of contacts. In normal use, use the CONTACT_ID value, but if your code gets a hint that the CONTACT_ID has changed (either missing, or unexpected contact name), you can use the LOOKUP_KEY to find the new contact-id.

You can use Contacts.getLookupUri(long, String) to get a URI you can always use to quickly find a contact no matter what its CONTACT_ID or LOOKUP_KEY actual values are.

marmor
  • 27,641
  • 11
  • 107
  • 150
  • 1
    But if you rename a contact or resync a online contact provider (and maybe more) this key may break as well sometimes... For me, based on my tests, it looks like the best solution currently is to use the primary contact's raw id, this one is stable in my test case. With this ID I then can query the current contact id and with this contact id I can query all data for the contact. What do you think of this solution? – prom85 Feb 26 '19 at 08:01
  • that's not good, by "primary" i assume you mean google raw-contact in most cases, those can change as well (sometimes there's a "sync failure" msg in the google contacts sync screen under "accounts" > google, that will trigger a re-sync of all google contacts, changing their raw-ids). if you change a name of a contact, it can give your app a "hint" that the contact-id might be wrong, so you'll use the getLookupUri method to find the new contact-id only to get the same id, so it's fine – marmor Feb 26 '19 at 08:04
  • Ok, thanks. You wrote "either missing, or unexpected contact name" - can an unexpected name really occur? In my test case, the old contact id is always removed and a new HIGHER one is created... In this case I could use the old lookup key to find the new contact id if the saved contact id can not be found anymore. – prom85 Feb 26 '19 at 08:07
  • that's more rare, but can happen, for example on rooted devices, a user can use Titanium Backup to restore your app after installing a new ROM, in which case all contacts have the same range of contact-ids (not higher then older) but are totally different, there are other similar examples – marmor Feb 26 '19 at 08:10
  • 1
    That's something intended by the user then and is acceptable. So in general, following is true: 1) a contact id will always link to the same contact (rare restore cases, cleans or similar excluded) 2) we can rely in this case that we can use the contact id to find a contact, if we don't find a contact we can find the new contact with the lookup_key => the some is done if you directly use `Contacts.getLookupUri(long, String)` to get contact – prom85 Feb 26 '19 at 08:13
  • i only gave one example i know of that may lead to the wrong contact-id, but there's no guarantee for your assumption in the contacts API, all they say is that the contact-id may change, they don't guarantee that the new contact-id will always be greater then any existing id. however, you can go with that, and fix in the future if you get reports, unless your project has 0 tolerance for such bugs – marmor Feb 26 '19 at 09:58
  • Ok, thanks. Additionally, if possible, a pair of `ContactsContract.Data.ACCOUNT_TYPE_AND_DATA_SET` && `ContactsContract.RawContacts.SOURCE_ID` seems like an even more constant data set than the lookup key, but only is available on api 21 and for synced accounts (although there is no guarantee here, but gmail e.g. provides unique constant source ids) – prom85 Feb 26 '19 at 10:16