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

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

Migrating to Jetpack Compose – an interop love story

Most of you are familiar with Jetpack Compose and its benefits. If you’re able to start anew and create a Compose-only app, you’re on the right track. But this talk might not be for you…
Watch Video

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engineer for Jetpack Compose
Google

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engin ...
Google

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engineer f ...
Google

Jobs

val contacts = Contacts(context)
.query()
.where {
(Name.GivenName startsWith "leo") and
(Email.Address { endsWith("gmail.com") or endsWith("hotmail.com") }) and
(Address.Country equalToIgnoreCase "us") and
(Event { (Date lessThan Date().toWhereString()) and (Type equalTo EventEntity.Type.BIRTHDAY) }) and
(Contact.Options.Starred equalTo true) and
(Nickname.Name equalTo "DarEdEvil") and
(Organization.Company `in` listOf("facebook", "FB")) and
(Note.Note.isNotNullOrEmpty())
}
.accounts(
Account("john.doe@gmail.com", "com.google"),
Account("john.doe@myspace.com", "com.myspace"),
)
.include { setOf(
Contact.Id,
Contact.DisplayNamePrimary,
Phone.Number
) }
.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(NewRawContact().apply {
name = NewName().apply {
givenName = "John"
familyName = "Doe"
}
organization = NewOrganization().apply {
company = "Amazon"
title = "Superstar"
}
emails.add(NewEmail().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.mutableCopy {
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)
.insertWithPermission()
...
.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
This tutorial is the second part of the series. It’ll be focussed on developing…
READ MORE
blog
We recently faced a problem with our application getting updated and reaching slowly to…
READ MORE
blog
A few weeks ago I started with a simple question — how to work…
READ MORE
blog
Splash screen is the initial screen you’ll see when you open your app. It…
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