Access to contacts is one of the most frequent use cases in Android applications. Even if your app is not a contact management app, there are various cases where you might need access to the device contacts (such as referring other users to the app).
For developers to access the device
contacts, they need to use ContentProviders. This introduces a lot of frustrations and complications. For someone that has never worked with
ContentProvider
s before, the documentation can be tedious to go through. The lack of a type-safe
API leads to repeated errors, developer frustration, along with a waste of time and resources for
the developer and the team.
Contact Store is a modern contacts Android API written in Kotlin. It utilises Coroutine's Flow to notify the developer for updates happening to the Contacts database.
Using Gradle:
repositories {
...
mavenCentral()
}
dependencies {
implementation 'com.alexstyl:contactstore:0.3.3'
// optional dependency for tests
testImplementation 'com.alexstyl:contactstore-test:0.3.3'
}
The following sample returns a list of all contacts in the device. Each contact will contain an id, a display name and whether they are starred or not:
val store = ContactStore.newInstance(application)
store.fetchContacts()
.collect { contacts ->
val contactString = contacts.joinToString(", ") {
"DisplayName = ${it.displayName}, isStarred = ${it.isStarred}, id = ${it.contactId}"
}
println("Contacts emitted: $contactString")
}
If you need to query specific details about a contact (commonly used in contact detail screens), the following sample returns a contact's Structured Names and phone numbers:
val store = ContactStore.newInstance(application)
store.fetchContacts(
predicate = ContactLookup(
inContactIds = listOf(contactId)
),
columnsToFetch = listOf(
ContactColumn.NAMES,
ContactColumn.PHONES
)
)
.collect { contacts ->
val contact = contacts.firstOrNull()
if (contact == null) {
println("Contact not found")
} else {
val phones = contact.phones
val contactString = contacts.joinToString(", ") { contact ->
"Names = ${contact.firstName} ${contact.middleName} ${contact.lastName} " +
"phones = ${phones.map { "${it.value} (${it.label})" }}"
}
println("Contacts emitted: $contactString")
}
}
Always make sure to query only the columns that you need. In the case where a property is accessed
which was not queried, an Exception
is thrown.
The following snippets show how to edit contacts in the device. execute()
.
val store = ContactStore.newInstance(application)
store.execute(SaveRequest().apply {
insert(MutableContact().apply {
firstName = "Paolo"
lastName = "Melendez"
phones.add(
LabeledValue(
value = PhoneNumber("555"),
label = Label.PhoneNumberMobile
)
)
})
})
In order to update a contact, you first need to get a reference to the contact from the store. Only the values queried will be updated. This is by design, in order to prevent accidental value overrides.
The following code modifies a contact's note:
val foundContacts = store.fetchContacts(
predicate = ContactLookup(inContactIds = listOf(5L)),
columnsToFetch = listOf(ContactColumn.NOTE)
).first()
if (foundContacts.isEmpty()) return // the contact was not found
val contact = foundContacts.first()
store.execute(SaveRequest().apply {
update(contact.mutableCopy().apply {
note = Note("To infinity and beyond!")
})
})
The following code shows how to delete a contact by id:
store.execute(SaveRequest().apply {
delete(contactId = 5L)
})
The optional com.alexstyl:contactstore-test
dependency provides a pure Kotlin implementation of ContactStore
, named TestContactStore
.
This implementation is meant for unit testing purposes without the need of running the tests on a real Android device or the use of frameworks such as Robolectric.
To report a specific problem or feature request, open a new issue on Github.
Apache 2.0. See the LICENSE file for details.
Made by Alex Styl. Follow @alexstyl on Twitter for future updates.