Blog Infos
Author
Published
Topics
, , ,
Published
Photo by Melinda Gimpel on Unsplash

Syncing phone contacts with your Android app can enhance the user experience by seamlessly integrating their contact lists.

In this blog, we’ll walk you through how to sync phone contacts using Kotlin, the preferred language for modern Android development.

Why Sync Contacts?

Syncing contacts allows your app to:

  • Personalize User Experience: Offer tailored services by accessing user contacts.
  • Enhance Functionality: Enable features like quick messaging or calling from within the app.
Step 1: Set Up Permissions

First, you’ll need to request permission to access the contacts. Add these permissions to your AndroidManifest.xml

<uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission>

If you’re targeting Android 6.0 (API level 23) or higher, you’ll need to request permissions at runtime as well. Here’s how you can do it:

// Check if the permission is already granted
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) 
    != PackageManager.PERMISSION_GRANTED) {
    
    // Request the permission
    ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_CONTACTS), REQUEST_CODE_READ_CONTACTS)
}
Step 2: Request Permission Result

Override onRequestPermissionsResult to handle the user’s response

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    
    if (requestCode == REQUEST_CODE_READ_CONTACTS) {
        if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // Permission granted, proceed with contact syncing
            syncContacts()
        } else {
            // Permission denied, show a message to the user
            Toast.makeText(this, "Permission denied to read contacts", Toast.LENGTH_SHORT).show()
        }
    }
}
Step 3: Access and Sync Contacts

Now, let’s write the code to read and sync contacts. We’ll use the ContentResolver to query contacts from the phone’s contact database.

private fun syncContacts() {
    val contactsList = mutableListOf<String>()

    // Access the Contacts content provider
    val cursor = contentResolver.query(
        ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
        null, null, null, null
    )

    cursor?.use {
        // Check if cursor contains data
        val nameIndex = it.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)
        val numberIndex = it.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)
        
        while (it.moveToNext()) {
            val name = it.getString(nameIndex)
            val number = it.getString(numberIndex)
            contactsList.add("Name: $name, Number: $number")
        }
    }

    // Now you have the contacts list, you can use it as needed
    displayContacts(contactsList)
}

private fun displayContacts(contacts: List<String>) {
    // Display the contacts in a TextView, Log, or any UI element
    contacts.forEach {
        Log.d("ContactSync", it)
    }
}

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

Kobweb:Creating websites in Kotlin leveraging Compose HTML

Kobweb is a Kotlin web framework that aims to make web development enjoyable by building on top of Compose HTML and drawing inspiration from Jetpack Compose.
Watch Video

Kobweb:Creating websites in Kotlin leveraging Compose HTML

David Herman
Ex-Googler, author of Kobweb

Kobweb:Creating websites in Kotlin leveraging Compose HTML

David Herman
Ex-Googler, author o ...

Kobweb:Creating websites in Kotlin leveraging Compose HTML

David Herman
Ex-Googler, author of Kob ...

Jobs

Step 4: Fetch Additional Details (Optional)

If you want to fetch more details, you’ll need to query additional ContentProvider URIs. Here’s how you might include more URIs:

private fun syncContacts() {
    val contactsList = mutableListOf<ContactDTO>()

    // Build query columns name array.
    val PROJECTION_DETAILS = arrayOf(
        ContactsContract.Data.RAW_CONTACT_ID,
        ContactsContract.Data.CONTACT_ID,
        ContactsContract.Data.MIMETYPE,
        ContactsContract.Data.DATA1, // number
        ContactsContract.Data.DATA2, // first name
        ContactsContract.Data.DATA3, // last name
        ContactsContract.Data.DATA5 // middle name
    )

    // Access the Contacts content provider
    // Query data table and return related contact data.
    val cursor = contentResolver.query(
        ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
        PROJECTION_DETAILS,
        null,
        null,
        null
    )

    val contactMap = mutableMapOf<Long, ContactDTO>()
    var contactRequestDTO: ContactDTO? = null

    if (cursor != null && cursor.count > 0) {
        cursor.moveToFirst()
        do {
            val contactId = cursor.getLongOrNull(cursor.getColumnIndex(ContactsContract.Data.CONTACT_ID)) ?: 0L

            // create a contactDTO based on CONTACT_ID to prevent duplicate contact for linked social media accounts, e.g. whatsapp
            contactDTO = if (contactMap[contactId] == null) {
                val newContact = ContactDTO()
                contactMap[contactId] = newContact
                newContact
            } else {
                contactMap[contactId]
            }

            // First get mimetype column value.
            val mimeType =
                cursor.getStringOrNull(cursor.getColumnIndex(ContactsContract.Data.MIMETYPE))

            val dataValueList = getColumnValueByMimetype(cursor, mimeType.orEmpty())
            dataValueList.forEach { (key, value) ->
                when (key) {
                 // if any contact has multiple mobile numbers
                    ContactsContract.CommonDataKinds.Phone.NUMBER -> {
                        if (!value.isNullOrBlankExt()) {
                            contactRequestDTO?.mobileNumberList?.add(value.orEmpty())
                        }
                    }

                    ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME -> {
                        contactRequestDTO?.firstName = value
                    }

                    ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME -> {
                        contactRequestDTO?.lastName = value
                    }

                    ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME -> {
                        contactRequestDTO?.middleName = value
                    }
                }
            }
        } while (cursor.moveToNext())

        contactsList.addAll(contactMap.values)
    }
    cursor?.close()

    // Now you have the contacts list, you can use it as needed
    displayContacts(contactsList)
}
/** Return data column value by mimetype column value.
 *  Because for each mimetype there has not only one related value,
 *  So the return map with key for that field, each string for one column value.
 **/
private fun getColumnValueByMimetype(
    cursor: Cursor,
    mimeType: String
): MutableMap<String, String?> {
    val params: MutableMap<String, String?> = HashMap()
    when (mimeType) {
        ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE -> {
            // Phone.NUMBER == data1
            val phoneNumber = cursor.getStringOrNull(
                cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)
            )
            params[ContactsContract.CommonDataKinds.Phone.NUMBER] = phoneNumber
        }

        ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE -> {
            // StructuredName.GIVEN_NAME == DATA2
            val givenName = cursor.getStringOrNull(
                cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME)
            )
            // StructuredName.FAMILY_NAME == DATA3
            val familyName = cursor.getStringOrNull(
                cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME)
            )

            // StructuredName.MIDDLE_NAME == DATA5
            val middleName = cursor.getStringOrNull(
                cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME)
            )
            params[ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME] = givenName
            params[ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME] = middleName
            params[ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME] = familyName
        }
    }

    return params
}

By extending your contact sync functionality, you can gather more comprehensive information from the user’s contact list, such as email addresses, contact photos, and addresses. This can significantly enrich the features of your app, providing a more personalized and engaging user experience.

Additional Note: Sync Contacts in a Background Thread Using ViewModel

To enhance performance and ensure a smooth user experience, it’s crucial to perform intensive operations like syncing contacts on a background thread. This avoids blocking the main UI thread and keeps your app responsive. One effective way to achieve this is by using ViewModel and LiveData from Android’s Architecture Components.

class ContactViewModel(application: Application) : AndroidViewModel(application) {
    
    private val _contactsLiveData = MutableLiveData<List<ContactDTO>>()
    val contactsLiveData: LiveData<List<ContactDTO>> get() = _contactsLiveData

    fun fetchContacts() {
        viewModelScope.launch(Dispatchers.IO) {
            val contactsList = fetchContacts()
            withContext(Dispatchers.Main) {
                _contactsLiveData.value = contactsList
            }
        }
    }

    private fun syncContacts(): List<ContactDTO> {
     ....
    }
}
Step 5: Handle Data Safely

Be sure to handle user data responsibly:

  • Follow Privacy Guidelines: Ensure you only access and use data for intended purposes.
  • Handle Data Securely: Store and transmit data securely if needed.
Conclusion

Syncing contacts in your Android app with Kotlin involves requesting permissions, accessing the contact database, and handling data responsibly. With these steps, you can integrate phone contacts into your app, enhancing its functionality and user experience.

Feel free to tweak the example code to suit your app’s needs.

I’ve also published this article on my blogspot. Please read and share for support.

https://androidacademic.blogspot.com/2024/08/advanced-contact-syncing-in-android.html?source=post_page—–67d7b4f30d5c——————————–

More from the Author

https://androidacademic.blogspot.com/?source=post_page—–67d7b4f30d5c——————————–

Thanks for reading this article. Hope you would have liked it!. Please clap, share, and subscribe to my blog to support.

This article is previously published on proandroidev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
Hi, today I come to you with a quick tip on how to update…
READ MORE
blog
Automation is a key point of Software Testing once it make possible to reproduce…
READ MORE
blog
Drag and Drop reordering in Recyclerview can be achieved with ItemTouchHelper (checkout implementation reference).…
READ MORE
Menu