Search code examples
androidandroid-contactsandroid-contentresolver

Duplicate phone numbers for each contacts android


In my application i want to show a list with my contacts and their phone number of each one. So i get the contacts from the ContentProvider and then all the numbers of the phone and then i match them by the contact id. code is the following

fun Context.retrieveAllContacts(): List<ContactData> {
    val result: MutableList<ContactData> = mutableListOf()
    contentResolver.query(
        ContactsContract.Contacts.CONTENT_URI,
        CONTACT_PROJECTION,
        null,
        null,
        ContactsContract.Contacts.DISPLAY_NAME_PRIMARY + " ASC"
    )?.use {
        if (it.moveToFirst()) {
            do {
                val contactId = it.getString(it.getColumnIndex(CONTACT_PROJECTION[0]))
                val firstName = it.getString(it.getColumnIndex(CONTACT_PROJECTION[2])) ?: ""
                val lastName = it.getString(it.getColumnIndex(CONTACT_PROJECTION[3])) ?: ""
                val initials = getInitials(firstName, lastName)
                result.add(ContactData(contactId, firstName, initials, mutableListOf(), null))
            } while (it.moveToNext())
        }
    }
    return result
}


fun Context.retrieveAllContactNumbers(): HashMap<String, ArrayList<String>> {
    val contactsNumberMap = HashMap<String, ArrayList<String>>()
    val phoneCursor: Cursor? = contentResolver.query(
        ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
        null,
        null,
        null,
        null
    )
    if (phoneCursor != null && phoneCursor.count > 0) {
        val contactIdIndex =
            phoneCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.CONTACT_ID)
        val numberIndex = phoneCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)
        while (phoneCursor.moveToNext()) {
            val contactId = phoneCursor.getString(contactIdIndex)
            val number: String = phoneCursor.getString(numberIndex)
            // check if the map contains key or not, if not then create a new array list with number
            if (contactsNumberMap.containsKey(contactId)) {
                contactsNumberMap[contactId]?.add(number)
            } else {
                contactsNumberMap[contactId] = arrayListOf(number)
            }
        }
        // contact contains all the number of a particular contact
        phoneCursor.close()
    }
    return contactsNumberMap
}

And after that i run a loop to make the correspondence

                val contactsListAsync = async { context?.retrieveAllContacts() ?: emptyList() }
                val contacts = contactsListAsync.await()
                val contactNumbersAsync = async { requireActivity().retrieveAllContactNumbers() }
                val contactNumbers = contactNumbersAsync.await()

                contacts.forEach { contactData ->
                    contactNumbers[contactData.contactId]?.let { numbers ->
                        numbers.forEach {
                            if (isValidIrisCellphoneNumber(PhoneNumberUtils.normalizeNumber(it)))
                                contactData.phoneNumbers.add(it)
                        }
                    }
                }

As i can see many contacts have the same number 2 or 3 or evan 4 times. I guess the "problem" comes from the retrieveAllContactNumbers() which fetchs numbers from ContactsContract.CommonDataKinds.Phone. Mostly, i want to know why is this happening and then how can i prevent it?

I also noticed this: I added a fake contact and i saw that in my list with 2 numbers. Perhaps it has to do with from where the contentResolver gets the numbers? For example Google account, SIM card, Internal storage...? I dont know...


Solution

  • That is expected by the way the Contacts Database is organized:

    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

    You are querying phones directly from the Data table, as you should, so you may get the same phone number from different RawContacts representing the same Contact.

    So for example, if you have Whatsapp installed on your device, and Google is syncing your contacts, the same phone number can and will be stored by 2 RawContacts per contact - one for each app.

    The easy solution would be to change your Map to:

    val contactsNumberMap = HashMap<String, Set<String>>()
    

    The Set will enforce uniqueness over the list of phones per contact.