Let's break down the process of syncing phone contacts with an Android app using Kotlin.
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.
In this blog, we'll walk you through how to sync phone contacts using Kotlin, the preferred language for modern Android development.
Photo by Melinda Gimpel on Unsplash |
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 permissions 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) } }
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.
Thanks for reading this article. Hope you would have liked it!. Please
share and subscribe to my blog to support.