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
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.
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