In this post we'll explore enabling users to select from a list of their android phone contacts & importing data from them into your own application.

We'll be using Room to store contacts, Recyclerviews with LiveData to display both a list of contacts from the phone to select for import and display contacts that have been imported, and databinding.

Grab the project from GitHub here to follow along.

Our sample application will allow us to create, store, and display information about contacts. It will have four activities:

  • a MainActivity with three buttons taking users to the other three
  • a ContactEdit activity, which lets us create contact records manually (contact fields include name, phone number, and email address)
  • a ContactSelect activity, which lets us see existing contacts in a list & click on them to edit them
  • a ContactImport activity (the focus of this blog post) which will allow us to search for phone contacts, select them, and use the name, phone number, and email address info from the phone contacts to populate the EditContact activity

main activity

contact edit

In addition to the four activity classes, we have several others. These are listed below, along with a description in some sort of logical grouping:

  • Database classes:
    • DatabaseClient - database client class
    • AppDatabase - Room database class
  • DAO (Data Access Object)
    • ContactDao - dao for operations on Room (contact) records
  • Repository
    • ContactRepository - repository class for performing CRUD operations on contacts (& other utilities for contacts)
  • Models
    • Contact - model class for contacts with properties, Room annotations, and getters & setters
    • ContactImportModel - model class for contact imports with fields used by the ContactImportModelAdapter and the ContactImport activity; each ContactImportModel fills out a row in the ContactImport activity's recyclerview for selection & import
  • ViewModel
    • ContactsViewModel - view model for a list of contacts, used by ContactSelect (primarily so we can set wrap LiveData around the list of contacts)
  • ViewHolder
    • ContactImportViewHolder - view holder for ContactImportModel objects, which we bind to the RecyclerView in the ContactImport activity
  • Adapters
    • ContactImportModelAdapter - ContactImportModel adapter - takes care of binding phone contacts (via ContactImportModel instances) to the RecyclerView in the ContactImport activity as well as updating the list of contacts in response to the user's query text
    • ContactAdapter - takes care of binding contacts to the RecyclerView in the ContactSelect activity as well as updating the contact list and launching the ContactEdit activity
  • ClickListener
    • CustomContactClickListener - click listener interface to launch the EditContact activity when a contact is clicked in ContactSelect

Whew! It looks like there's a lot going on, but much of this is just supporting code & a barebones application for us to use our contact importing code in. Specifically, we'll be focusing on ContactImport (much of the actual code of interest), ContactImportModel, ContactImportViewHolder, ContactImportModelAdapter, and ContactEdit.

Before we do, let's take a tour of the rest of the code to understand what's going on there. This list is just about understanding what the baseline moving pieces are and how they fit together - we'll moslty gloss over the complexity in these parts. Feel free to skim or skip this part until we get to the section on ContactImportModel below.

  • MainActivity - not much going on here at all. Just onClick methods to launch the other activities.
  • AppDatabase - very little meat in this one. We're just defining our Room database, where we'll be storing our contact records. We give it a member for the name of the table and for its dao.
  • DatabaseClient - this is a simple database client class, like one you'll see in a many Room applications. This will facilitate our use of a singleton pattern so we can have just one instantiated instance of the database for every access.
  • ContactDao - about what you'd expec to see in a contact dao in here. Note how we're wrapping the List returned by getAllContactsLiveData with LiveData - this is for our implementation of LiveData so that our list of contacts in ContactSelect is updated immediately when we make any changes, such as deleting a contact.
  • ContactRepository - holder of repository methods to instantiate the CRUD method signatures we defined in ContactDao.
  • Contact - our model class for contacts. This has member fields for ID, name, phone number, email address, and country code for phone number. The inclusion of country code is for more robust handling of international phone numbers.
  • ContactsViewModel - our view model for a list of contacts, used by the ContactSelect activity.
  • ContactSelect - activity class to display the created contacts in a recyclerview for selection. We can only come here via the Contact Select button on the MainActivity.
    • Everything in onCreate below the "// set ui pieces"... comment is only there to handle the case when no contacts have been created yet. In this case, we make visible the text view letting the user know that there are no contacts yet, and also make visible a button to enable them to create their first contact.
  • ContactsViewModel - view model to wrap LiveData around a list of contacts for ContactSelect
  • ContactAdapter - the RecyclerView adapter used by ContactSelect to bind a contact to each row and implement our CustomContactClickListener to launch ContactEdit for a selected contact.
  • CustomContactClickListener - just an interface for our click listener

 

With that out of the way, let's move on to the classes which make up the real focus of this article.

 

ContactImportModel - this is our model class for contact imports - these will store data for contacts to be imported while we're searching for them &. The fields in here are name, phone number, and email 

It implements Serializable so that it can be used as an extra when passed from ContactImport to ContactEdit. It also implements SortedListAdapter, which is an implementation of a RecyclerView Adapter that uses a SortedList - this is so that we can search contacts to import and filter them using a SearchView. SortedListAdapter was created by this person - https://gist.github.com/Wrdlbrnft - and lives on github here: https://gist.github.com/Wrdlbrnft/1b6233e99c6b7d54c204e61c1c9c0bf3.

The isSameModelAs and isContentTheSameAs methods are required when implementing SortedListAdapter to check models for being the same model and for having the same contents respectively.

 

ContactImportViewHolder - view holder for ContactImportModel objects, which we bind to the RecyclerView in the ContactImport activity. We're using databinding to bind to the contact_import_model.xml layout, which will represent one phone contact you can impoort, displayed in a RecyclerView in the ContactImport activity. We're also setting a listened so that when one is selected, we are taken to our desired resulting activity - ContactEdit within the selected phone contact.

 

ContactImportModelAdapter - this takes care of binding phone contacts (via ContactImportModel instances) to the RecyclerView in the ContactImport activity. It also supplies the listener for when a ContactImportModel is selected and a cursor which is going to be used by ContactImport to query phone contacts.

 

ContactImport - this is the activity class for importing contacts and where most of the action is that we're interested in.

We implement SearchView.OnQueryTextListener, which gives us the onQueryTextChange and onQueryTextSubmit methods to override. We don't use onQueryTextSubmit in favor of onQueryTextChange - rather than waiting for a formal submit, we just update the result set every time the query text is changed. Implemening SearchView.OnQueryTextListener also lets us call searchView.setOnQueryTextListener(this), passing a ContactImport instance as a query text listener to our SearchView.

We also implement SortedListAdapter.Callback for SortedListAdapter functionality. This gives us onEditStarted and onEditFinished methods, which are part of the Callback interface defined in the SortedListAdapter class. These are called whenever the query text is changed and handle animation and positioning of the phone contact list during querying.

During onCreate we set up our data binding with the activity_contact_import.xml layout. We also construct our ContactImportModelAdapter, providing a comparator and a listener (which starts the ContactEdit activity with an intent that contains a serialized ContactImportModel. We then call setAdapter on our binding with our ContactImportModelAdapter. We call our mayRequestContacts method to confirm that this app has permission to view phone contacts on the device. If we don't have permssion, we'll request it from the user by calling requestPermissions. This requires us to have inluded in our AndroidManifest.xml file (contents of this covered later).

Finally - still in onCreate - we create an instance of FetchContacts and execute it to load all phone contacts to search from. FetchContacts extends from AsyncTask so we can perform the search in the background. We use a ContentResolver and a Cursor to query phone contacts using the ContactsContract.CommonDataKinds.Phone.CONTENT_URI uri. Name and phone number are in column indexes 1 and 2. Email addresses are a bit more tricky since there may be more than one - the code block following the "// get just the first email if there are more than one" comment gets us just the first email address in case there are more than one. We return the list of all contacts in a list so the adapter can use it and sort them with SortContacts.

The onCreateOptionsMenu method inflates the ui and with our SearchView, which is provided by the main_menu.xml menu layout - check that out further below. It then sets the query text listener to this SearchView.

Taking a closer look at the text query code - in onQueryTextChange we check the value of contactsLoaded, which is only set to true once all contacts have been loaded. In FetchContacts's onPostExecute we call searchItem.setVisible(true) to make the SearchView visible. These are both there to prevent users from querying contacts before they've all been loaded. With all contacts loaded, any time query text is changed we call onQueryTextChange, and this in turn calls our filter method to filter or expand the filtered list based on the entered text.

 

ContactEdit - the activity for creating a new contact. When we've selected a contact to import, we'll be taken here with data from the selected phone contact populating the relevant EditText fields. In this case we'll have a ContactImportModel in the intent passed through as a serializable extra. If there is a ContactImportModel to retrieve, we populate the name, phone number, and email EditText fields with the corresponding fields from the ContactImportModel, and then proceed as though we're creating a new contact.

We're also taken here to edit existing contacts when one is selected from the ContactSelect activity. In this case we'll be passed in an int extra in the intent called editContactID, which we use to fetchContactById. If we're not passed an ID, we set editContactID to -100 which indicates that we're working on a new contact (all saved contacts in the app have IDs >= 0). We keep track of whether we're in either case via the isNewContact and isImported boolean variables.

There is some additional content for validating input data beneath the "// validation code starts here" comment. There's also come complexity added by the country code picker for robust phone numbers. This is provided by the neat android library on github here: https://github.com/hbb20/CountryCodePickerProject.

 

contact import

 

imported contact

contact select

We're just about done, but before we wrap up let's touch on our layouts:

  • activity_main - this one's nothing but a container for buttons to the other contacts
  • activity_contact_edit - nothing too fancy in here (no databinding or anything) - just some EditTexts and a buttons to save or delete a contact
  • activity_contact_select - this one uses a RecyclerView in a ScrollView for us to scroll through contacts; we use databinding with the ContactAdapter to provide data to contact_list_row instances within the RecyclerView
    • contact_list_row - databinding binds to a Contact & our custom click listener takes us to ContactEdit when you click one; note the expressions we use to display contact info in each contact's CardView's android:text
  • activity_contact_import - this uses a search toolbar and RecyclerView to hold our device contacts' info and filter them as we input search text
    • contact_import_model - databinding binds to a ContactImportModel & our custom click listener takes us to ContactEdit when you click one; text is provided by the ContactImportModel's text field

 

And that's all! With this you have all the pieces you need to query for, select, and import information from device contacts into your own application. This can be extended to querying for & imporitng information from other content providers in a similar way. Now go out there and enjoy some importing!

blog comments powered by Disqus