Blog Infos
Author
Published
Topics
,
Published

Dawn of a new era

https://www.shutterstock.com/image-vector/hands-smartphones-doodle-set-human-palms-1850275141

 

https://en.wikipedia.org/wiki/Open_Handset_Alliance#/media/File:Open_Handset_Alliance_logo.svg

 

Deprecation of Contacts

https://www.shutterstock.com/image-photo/time-say-goodbye-typed-words-on-531209353

Hello ContactsContract

https://www.shutterstock.com/image-vector/international-group-people-saying-hi-different-1182493903

 

In just over a year since launch, Android 2.0 Eclair (API 5) was released. There was a clear theme for the 2.0 version. The Android team overhauled the initial contacts and accounts mechanism set in place with the android.provider.Contacts and replaced it with android.provider.ContactsContract.

https://developer.android.com/reference/android/provider/Contacts

 

The goal was to fix the lack of scalability put in place by the deprecated counterpart. It worked. To this day, 26 API versions later, ContactsContractis alive and well in Android 12 (API 31). The Android team accomplished their mission. The architecture of the new contacts API was simple and it scaled well. This figure shows it all.

https://developer.android.com/guide/topics/providers/contacts-provider

The above figure is taken from the official Android developer docs. I’ll also copy and paste the description of the figure as I am not able to word it any better.

 

 

ContactsContract is simple, yet complex

https://www.shutterstock.com/image-photo/man-think-how-solve-mathematical-problem-766012597

// First, we define a structure to store contact data we want to retrieve
data class ContactData(
val contactId: Long,
val name: String,
val phoneNumber: List<String>,
val avatar: Uri?
)
// Here are the functions to retrieve all contacts matching the search pattern
fun Context.retrieveAllContacts(
searchPattern: String = "",
retrieveAvatar: Boolean = true,
limit: Int = -1,
offset: Int = -1
): List<ContactData> {
val result: MutableList<ContactData> = mutableListOf()
contentResolver.query(
ContactsContract.Contacts.CONTENT_URI,
CONTACT_PROJECTION,
if (searchPattern.isNotBlank()) "${ContactsContract.Contacts.DISPLAY_NAME_PRIMARY} LIKE '%?%'" else null,
if (searchPattern.isNotBlank()) arrayOf(searchPattern) else null,
if (limit > 0 && offset > -1) "${ContactsContract.Contacts.DISPLAY_NAME_PRIMARY} ASC LIMIT $limit OFFSET $offset"
else ContactsContract.Contacts.DISPLAY_NAME_PRIMARY + " ASC"
)?.use {
if (it.moveToFirst()) {
do {
val contactId = it.getLong(it.getColumnIndex(CONTACT_PROJECTION[0]))
val name = it.getString(it.getColumnIndex(CONTACT_PROJECTION[2])) ?: ""
val hasPhoneNumber = it.getString(it.getColumnIndex(CONTACT_PROJECTION[3])).toInt()
val phoneNumber: List<String> = if (hasPhoneNumber > 0) {
retrievePhoneNumber(contactId)
} else mutableListOf()
val avatar = if (retrieveAvatar) retrieveAvatar(contactId) else null
result.add(ContactData(contactId, name, phoneNumber, avatar))
} while (it.moveToNext())
}
}
return result
}
private fun Context.retrievePhoneNumber(contactId: Long): List<String> {
val result: MutableList<String> = mutableListOf()
contentResolver.query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null,
"${ContactsContract.CommonDataKinds.Phone.CONTACT_ID} =?",
arrayOf(contactId.toString()),
null
)?.use {
if (it.moveToFirst()) {
do {
result.add(it.getString(it.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)))
} while (it.moveToNext())
}
}
return result
}
private fun Context.retrieveAvatar(contactId: Long): Uri? {
return contentResolver.query(
ContactsContract.Data.CONTENT_URI,
null,
"${ContactsContract.Data.CONTACT_ID} =? AND ${ContactsContract.Data.MIMETYPE} = '${ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE}'",
arrayOf(contactId.toString()),
null
)?.use {
if (it.moveToFirst()) {
val contactUri = ContentUris.withAppendedId(
ContactsContract.Contacts.CONTENT_URI,
contactId
)
Uri.withAppendedPath(
contactUri,
ContactsContract.Contacts.Photo.CONTENT_DIRECTORY
)
} else null
}
}
private val CONTACT_PROJECTION = arrayOf(
ContactsContract.Contacts._ID,
ContactsContract.Contacts.LOOKUP_KEY,
ContactsContract.Contacts.DISPLAY_NAME_PRIMARY,
ContactsContract.Contacts.HAS_PHONE_NUMBER
)

This code snippet is taken from https://github.com/aminography/CommonUtils/blob/master/library
/src/main/java/com/aminography/commonutils/ContactUtils.kt

I take no credit for this code snippet. I’m just using it as an example for this blog.

 

Take, take, and finally, give back

https://www.shutterstock.com/image-photo/give-back-385772176

 

 

Rebirth of Contacts

https://www.shutterstock.com/image-illustration/wizard-summoning-phoenix-hell-digital-art-1402715897

 

Revisiting how to retrieve all Contacts
val contacts = Contacts(context).query().find()

To simply search for Contacts, yielding the exact same results as the native Contacts app,

val contacts = Contacts(context)
.broadQuery()
.whereAnyContactDataPartiallyMatches(searchText)
.find()

Job Offers

Job Offers


    Lead Android Engineer

    ASOS
    London
    • Full Time
    apply now

    Senior Android Engineer – Big Release Team

    Zalando SE
    Berlin
    • Full Time
    apply now

    Developer (m/w/d) Backend/ Mobile

    Payback GmbH
    Cologne, Germany
    • Full Time
    apply now
Load more listings

OUR VIDEO RECOMMENDATION

Behind the Curtains

All smartphones have cameras, and we know we can use specific APIs to get amazing shots. But are they the best cameras? Probably not! What if we wanted to drive an external camera, much more powerful than a smartphone? How would we connect to it, and how would we trigger a shot? This and much more…
READ MORE

Jobs

val contacts = Contacts(context)
.query()
.where(
(Fields.Name.GivenName startsWith "leo") and
((Fields.Email.Address endsWith "gmail.com") or (Fields.Email.Address endsWith "hotmail.com")) and
(Fields.Address.Country equalToIgnoreCase "us") and
((Fields.Event.Date lessThan Date().toWhereString()) and (Fields.Event.Type equalTo Event.Type.BIRTHDAY)) and
(Fields.Contact.Options.Starred equalTo true) and
(Fields.Nickname.Name equalTo "DarEdEvil") and
(Fields.Organization.Company `in` listOf("facebook", "FB")) and
(Fields.Note.Note.isNotNullOrEmpty())
)
.accounts(
Account("jerry@gmail.com", "com.google"),
Account("jerry@myspace.com", "com.myspace"),
)
.include(Fields.Contact.Id, Fields.Contact.DisplayNamePrimary, Fields.Phone.Number, Fields.Phone.NormalizedNumber)
.orderBy(ContactsFields.DisplayNamePrimary.desc())
.offset(0)
.limit(5)
.find()
Log.d(
"Contacts",
contacts.joinToString("\n\n") { contact ->
"""
ID: ${contact.id}
Display name: ${contact.displayNamePrimary}
Display name alt: ${contact.displayNameAlt}
Photo Uri: ${contact.photoUri}
Thumbnail Uri: ${contact.photoThumbnailUri}
Last updated: ${contact.lastUpdatedTimestamp}
Starred?: ${contact.options?.starred}
Send to voicemail?: ${contact.options?.sendToVoicemail}
Ringtone: ${contact.options?.customRingtone}
Aggregate data from all RawContacts
-----------------------------------
Addresses: ${contact.addressList()}
Emails: ${contact.emailList()}
Events: ${contact.eventList()}
Group memberships: ${contact.groupMembershipList()}
IMs: ${contact.imList()}
Names: ${contact.nameList()}
Nicknames: ${contact.nicknameList()}
Notes: ${contact.noteList()}
Organizations: ${contact.organizationList()}
Phones: ${contact.phoneList()}
Relations: ${contact.relationList()}
SipAddresses: ${contact.sipAddressList()}
Websites: ${contact.websiteList()}
-----------------------------------
""".trimIndent()
}
)
// There are also aggregate data functions that return a sequence instead of a list.
// Each Contact may have more than one of the following data if the Contact is made up of 2 or more RawContacts;
// name, nickname, note, organization, sip address.
There’s a lot more
val emails = Contacts(context).data()
.query()
.emails()
.where(Fields.Email.Address endsWith "gmail.com")
.orderBy(Fields.Email.Address.desc(ignoreCase = true))
.offset(0)
.limit(20)
.find()

It’s not just for emails. It’s for all common data kinds (including custom data);

 

val insertResult = Contacts(context)
.insert()
.rawContacts(MutableRawContact().apply {
name = MutableName().apply {
givenName = "John"
familyName = "Doe"
}
organization = MutableOrganization().apply {
company = "Amazon"
title = "Superstar"
}
emails.add(MutableEmail().apply {
address = "john.doe@amazon.com"
type = Email.Type.WORK
})
})
.commit()

Or alternatively, in a more Kotlinized style,

val insertResult = Contacts(context)
.insert()
.rawContact {
setName {
givenName = "John"
familyName = "Doe"
}
setOrganization {
company = "Amazon"
title = "Superstar"
}
addEmail {
address = "john.doe@amazon.com"
type = Email.Type.WORK
}
}
.commit()
Contacts(context)
.update()
.contacts(johnDoe.toMutableContact().apply {
setOrganization {
company = "Microsoft"
title = "Newb"
}
emails().first().apply {
address = "john.doe@microsoft.com"
}
})
.commit()
Contacts(context)
.delete()
.contacts(johnDoe)
.commit()
There’s even more…
launch {
val contacts = Contacts(context)
.queryWithPermission()
...
.findWithContext()
val deferredResult = Contacts(context)
.insert()
...
.commitAsync()
val result = deferredResult.await()
}
There’s too much…
P.S.

Hopefully, this API provides a modern answer to all of these ancient StackOverflow questions.

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
Nowadays authentication has become common in almost all apps. And many of us know…
READ MORE
blog
Collections are a set of interfaces and classes that implement highly optimised data structures.…
READ MORE
blog
Hi, today I come to you with a quick tip on how to update…
READ MORE
blog

How to animate BottomSheet content using Jetpack Compose

Early this year I started a new pet project for listening to random radio…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu