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...
That is expected by the way the Contacts Database is organized:
Contacts
- each entry represents one contact, and groups together one or more RawContacts
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 entriesData
- 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.