Android Contacts & Jetpack Compose

Enrico Mazzucchelli
6 min readJan 8, 2023

--

In this article, we’ll see how to show the list of contacts and the relative details using jetpack compose as ui kit.

The idea is to start with a simple implementation and adding features like pagination and so on in next articles.

Contacts Provider Overview

The Contacts Provider is essentially a Content Provider that works as repo for our contacts.

Contacts table structure (source: Android Developer Site)

We have 3 tables:

Every entity has an _ID column and the necessary join columns. To fetch the data, we will use the ContentResolver.query method. This api returns a cursor that we can use to iterate over the selected records. Keep in mind that under the hood there is an SQLlite database, so when you want to fetch one or more tuple, you don’t use a “fetch all and filter in my code” approach, but perform the most restrictive selection and projection as possible.

The query definition

N.B.: We want to define this bridge function in Kotlin because in this way we ca use named argument & default values (without passing multiple null values).

Coding Our App

Our first app version can be simple: a first screen (home) that show a list of our contacts. Every item in our list has the classic circle with the initial and the display name of the contact. Similar to the app Contacts on our Android device, we want to have a sticky header with the initial. When the user clicks on a specific contact, a detail screen display the phone number (one or more), emails and websites related to the contact.

Our stack: jetpack compose, dagger-hilt, navigator, viewModel & lifecycle and the relative jetpack compose integration libraries. We can start without domain layer (use cases), because the app is simple.

As usual: in a real world app the complexity will be higher than in our example. Domain layer and a multi-module feature-based approach should be always evaluated.

1. The Data Layer

We can start with the definition of our contact provider (the data provider of the contacts).

To perform a query, we have to specify the source (the provider uri), the columns required (projection), the selection and finally the sorting. To help the mapping phase, have defined some extension function, like a Cursor.map, to iterate over the cursor.

N.B.: Iterate over all the records is a simplified approach. Sometimes you have thousands of records! A better approach could be to perform a query with a paginated approach (we will ses how to implement our contacts fetch with this feature in the next articles).

getContacts implementation

Finally, we have to define a mapping between a single cursor record and our model (Contact).

Cursor to Contact mapper

What about the contact details? In our app we what to show (if present) the phone number, the email, the website and the source of the data (the account/application label). Every Contact can have 1…N RawContact and every RawContact can have 1…N Data. Each Data has a mimetype that can be used to understand the type of the record and the relative fields mapping. In our data layer we have defined a sealed interface ContactDetails.

ContactDetails definition

The enum can also be deleted (with a sealed interface you have a defined hierarchy and you can do a when with cast on all the types). We have defined 4 data classes that implements the ContactsDetails: Name, Phone, Email and Website. There are many other mimetypes defined: feel free to add new implementations of the ContactDetails to map your needs!

We can now define our query to fetch details. Every contact in our data definition can have a list of ContactDetails items. The ContactsContract.DATA defines multiple columns DATA1, DATA2, … DATA15: these are generic data columns, the meaning is mimetype specific. For example, the constant Phone.NUMBER represents the column that contains the phone number, but its value is DATA1: so project the DATA1 generic column is like projecting Phone.NUMBER. However, the same projection can be also used for Email.ADDRESS. With this approach you can project the generic data columns in a single query and, checking the desired mimetype, define the meaning of the column content.

getContactDetails implementation

The Cursor.asContactDetails() extension function checks the mimetype and, based on this value, defines what type of ContactDetails implementation instantiate.

Mapping logic

The full code of the mapping can be found in the simple app (file cursorToModelMappers.kt). Finally, we have to define our ContactsRepository.

The ContactsRepository definition

The ContactsRepository exposes a property that is a flow of a Result of contacts (AppFlow<T> is a type alias of Flow<Result<T>). Result is a sealed interface with 3 implementation: Loading, Success, Error. Our first version of the ContactsRepository can be:

The repo implementation

The ContactsRepository works like a cache of the loaded contacts and, until an explicit refresh, the UI Layer observes the same list of contacts. Instead, the details are always required to the local database. However you can also modify this approach and creare a method that return a cold flow or a Result with your contacts always fetched by the content provider.

2. The UI Layer

To fetch our contact, we need to handle the permission READ_CONTACTS.
The first step is to define the permission in our manifest:

AndroidManifest section

However this is not enough: the READ_CONTACTS has a protection level dangerous and requires the runtime permission management. To help the community in this step, Google provides Jetpack Compose Permissions library under the Accompanist project.

The integration of this library is really easy: we need to remember a specific permission state (PermissionState) and, when we want, start the permission request (launchPermissionRequest).

In our app we want to show a box with a message (rationale) and a button to start the permission grant flow. The rationale explains to the user why we need to ask the permission and what is the impact in case of reject.

Here a composable @Preview:

Preview of our permission box

We can start with the HomeViewModel. We have to collect / map the contacts form the ContactsRepository.

Our viewModel collects and group our contacts. The @Composable HomeScreen function collects as state the uiState exposed by the viewModel.

The HomeContent contains a loader indicator, a pull-to-refresh indicator and the list of our contacts. We want to implement a list of contact with a sticky header that stay fixed on scrolling and, for every single items, the classic circle with the initial and the display name.

Our list implementation

Also in this case we will use the useful preview and a set of mock data to see the result of our implementation.

When the user clicks on a contact, we have to navigate to the details screen. The screen has an header and a list with phone number, email & so on. Essentially, the viewModel maps every ContactDetails record in a ContactAttribute.

With a @Preview, we can see the result:

Summary

In this article we have seen how to fetch the contacts using the ContentResolver and how to fetch and map details of a specific contactId. Finally, we have implemented our ui using jetpack compose.

You can find the app source code here:

As usual, feel free to add your feedback and suggestions in the comment section below.

Thanks for your support and your time :)

--

--

Enrico Mazzucchelli

Software development is my passion and I love to find creative and efficient ways to do it :)