How to remove duplicate entries in Android Contacts App which gets added when adding new contacts programatically?

Viewed 113

I have an app which inserts/updates a contact in the phone book using below code, the code works but the problem I am facing is if there is similar contact with the same phone number eg. a WhatsApp, dou, Viber etc. contact then those app contact takes over my contacts DisplayName and merges together but due to this, we are having multiple duplicate entries in some versions of android like (Samsung, LG, MI A5 stock android) etc.

But works on some phones like MI Max and a few others, does anyone have a solution to this problem or is there I am missing some fields that need to be present to avoid duplicate contacts.

private fun insertContact(contact: Contact): Boolean {
    try {
        val operations = ArrayList<ContentProviderOperation>()
            ContentProviderOperation.newInsert(RawContacts.CONTENT_URI).apply {
                withValue(RawContacts.ACCOUNT_NAME, "abcd@gmail.com")
                withValue(RawContacts.ACCOUNT_TYPE, "google.com")
                operations.add(build())
            }

        ContentProviderOperation.newInsert(Data.CONTENT_URI).apply {
            withValueBackReference(Data.RAW_CONTACT_ID, 0)
            withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE)
            withValue(StructuredName.GIVEN_NAME, contact.firstName)
            withValue(StructuredName.FAMILY_NAME, contact.lastName)
            withValue(StructuredName.SUFFIX,  "AppName")
            operations.add(build())
        }

        addUpdatePhone(operations, contact.phoneNumbers)
        //similar function for other fields email, address, birthday, profilePic etc

        val results = context.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
        return true
    } catch (e: Exception) {
        LOG.e( "Error inserting contact")
        return false
    }
}

private fun updateContact(contact: contact, rawContactId: String): Boolean {
    try {
        val operations = ArrayList<ContentProviderOperation>()
        ContentProviderOperation.newUpdate(Data.CONTENT_URI).apply {
            val selection = "${Data.RAW_CONTACT_ID} = ? AND ${Data.MIMETYPE} = ?"
            val selectionArgs = arrayOf(rawContactId, StructuredName.CONTENT_ITEM_TYPE)
            withSelection(selection, selectionArgs)
            withValue(StructuredName.GIVEN_NAME, contact.firstName)
            withValue(StructuredName.FAMILY_NAME, contact.lastName)
            withValue(StructuredName.SUFFIX, "AppName")
            operations.add(build())
        }

        addUpdatePhone(operations, contact.phoneNumbers, true, rawContactId)
        //similar function for other fields email, address, birthday, profilePic etc

        context.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
        return true
    } catch (e: Exception) {
        LOG.e("Error updating contact")
        return false
    }
}

private fun addUpdatePhone(operations: ArrayList<ContentProviderOperation>, phoneNumbers: List<PhoneNumber>, isUpdate: Boolean = false, rawContactId: String = "") {
    if(isUpdate) {
        //delete old data with the given raw_contact_id
        ContentProviderOperation.newDelete(Data.CONTENT_URI).apply {
            val selection = "${Data.RAW_CONTACT_ID} = ? AND ${Data.MIMETYPE} = ? "
            val selectionArgs = arrayOf(rawContactId, Phone.CONTENT_ITEM_TYPE)
            withSelection(selection, selectionArgs)
            operations.add(build())
        }
    }

    phoneNumbers.forEach {
        //add new rows of phone number for the given raw_contact_id
        ContentProviderOperation.newInsert(Data.CONTENT_URI).apply {
            if(isUpdate) {
                withValue(Data.RAW_CONTACT_ID, rawContactId)
            } else {
                withValueBackReference(Data.RAW_CONTACT_ID, 0)
            }

            withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE)
            withValue(Phone.TYPE, it.type)
            withValue(Phone.LABEL, it.label)
            withValue(Phone.NUMBER, it.value)
            withValue(Phone.NORMALIZED_NUMBER, it.value.normalizeNumber())
            operations.add(build())
        }
    }
}

//similar functions as above for other fields
private fun otherUpdateFunAsAbove() {}
1 Answers

After adding a new RawContact, Android needs to make a decision if this is a brand new Contact or if there's already an existing contact that represents the same person.

Android has an algorithm, which changes and evolved with different OS versions, and might have been tweaked by makers like Samsung, but usually it looks for a very similar name with some other item (such as phone or email) that is either identical or very close.

In that case it'll merge the two contacts using RawContact Aggregation. Apps can control this process via AggregationExceptions in which an app can state "Keep these two RawContacts separate" or "Keep these two RawContacts merged" regardless of that algorithm.

So what you're explaining would be normal behavior, your original name should still had been kept within the RawContacts.

In any way, I would not recommend trying to find a RawContact (i.e. in your selection clause) using an ID + Name, instead you should use just the RawContact ID. If you can't find that RawContact ID, this still doesn't mean you should create a new one, instead use the RawContact's lookupUri that you need to store in your app, and get a possibly new ID from that, in a way similar to the approach suggested for Contacts.

Related