Android Contacts : The Tough Part

Kirtan Thakkar

Kirtan Thakkar

Life is all about learning

If you have ever been trying to retrieve a list of contacts with the phone numbers and emails in your application, you might have struggled initially. The same happened with me. But, when I decided to dive deeper, it was easy to understand (actually!). We will go through the basic structure to understand how they are stored (so that we can query correctly) and code snippet to quickly retrieve the list of contacts.

Let's quickly understand first how all the queries are working internally to fetch the data we want.

The basic structure of the contacts stored on your device is like this:

Let's understand what each of them is storing.

  1. Contact
  • ContactsContract.Contacts table
  • Rows represnts different people aggregated based on the raw contacts.
  1. Raw Contact
  • ContactsContract.RawContacts table
  • It contains the data of a user for a specific account type. Say there are 2 different account for your number on your device, one for google account and other for whatsapp. These both are 2 different raw contacts which will be aggregated in the Contact table. You might have seen a special row for the whatsapp number when you see the contact details in your phone's contacts application. So, that's the 2 different raw contacts aggregated based on the data in each of them to represent the single contact.
  1. Data
  • ContactsContract.Data table
  • It stores the data of a raw contact like email addresses and phone numbers in each rows. Say one record for your phone number and another for your email address.

So, you now understood what tables will get you what types of data. Now, let's see what are some common fields which can help us with the data we want.

Each table's _ID column is the primary key of the table. So, ContactsContract.Contacts table's _ID will give you unique id of a single contact. But you may not use _ID of ContactsContract.RawContacts to get the data of the same person from the ContactsContract.Contacts table. It might be different.

Below points will clear how they all are connected:

  • ContactsContract.Data table has a RAW_CONTACT_ID column which says that this data record is of which Raw Contact.
  • ContactsContract.Data table and ContactsContract.RawContacts table has a CONTACT_ID column which is the _ID of the ContactsContract.Contacts table.

Refer to the below image. It will help you understand this visually.

Now, let's understand how the data is stored in the ContactsContract.Data table, because that is the place where we need to query for the actual data.

ContactsContract.Data table has the MIMETYPE column which defines which type of data is stored in that record, for example phone number or email address. DATA1 column contains the actual data based on the mime type defined. So, for example it will contain an actual phone number of the person if mime type is phone.

I found the below image on the stackoverflow post which helped me a lot to understand. Here is the image:

This will make many of the things pretty clear. So, now lets code it to access the list of contacts really fast.

Please refer to the below function which will retrieve the list of contacts in the Friend bean/model class.

ContactUtils.java
// load the list of contacts with name, email and display photo of contacts
// who have either phone numebr or an email address stored on the device's
// contact book
public void loadContacts() {
// map to store and update the data as we loop through all type of data
HashMap<Integer, Friend> tempContacts = new LinkedHashMap<>();
// Loading All Contacts
final String[] PROJECTION = new String[]{
ContactsContract.Data.CONTACT_ID,
ContactsContract.Data.DISPLAY_NAME,
ContactsContract.Data.DATA1,
ContactsContract.Data.PHOTO_URI,
ContactsContract.Data.MIMETYPE
};
long start = System.currentTimeMillis();
Log.d(TAG, "Contacts query cursor initialized. Querying..");
ContentResolver cr = getContentResolver();
// We need the record from the ContactsContract.Data table if
// the mime type is Email or Phone
// And the sort order should be by name
Cursor cursor = cr.query(
ContactsContract.Data.CONTENT_URI,
PROJECTION,
ContactsContract.Data.MIMETYPE + " = ?" +
" OR " +
ContactsContract.Data.MIMETYPE + " = ?",
new String[]{
ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE,
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE
},
"lower(" + ContactsContract.Data.DISPLAY_NAME + ")"
);
Log.d(TAG, "Total Rows :" + cursor.getCount());
try {
final int idPos = cursor.getColumnIndex(ContactsContract.Data.CONTACT_ID);
final int namePos = cursor.getColumnIndex(ContactsContract.Data.DISPLAY_NAME);
final int photoPos = cursor.getColumnIndex(ContactsContract.Data.PHOTO_URI);
final int emailNoPos = cursor.getColumnIndex(ContactsContract.Data.DATA1);
final int mimePos = cursor.getColumnIndex(ContactsContract.Data.MIMETYPE);
while (cursor.moveToNext()) {
int contactId = cursor.getInt(idPos);
String emailNo = cursor.getString(emailNoPos);
String photo = cursor.getString(photoPos);
String name = cursor.getString(namePos);
String mime = cursor.getString(mimePos);
// If contact is not yet created
if (tempContacts.get(contactId) == null) {
// If type email, add all detail, else add name and photo (we don't need number)
if (mime.equals(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE))
tempContacts.put(contactId, new Friend(name, emailNo, photo));
else
tempContacts.put(contactId, new Friend(name, null, photo));
} else {
// Contact is already present
// Add email if type email
if (mime.equals(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE))
tempContacts.get(contactId).setEmail(emailNo);
}
}
} finally {
cursor.close();
Log.d(TAG, "Cursor closed..");
}
long end = System.currentTimeMillis();
float diffSeconds = (float) ((end - start) / 1000.0);
Log.d(TAG, tempContacts.size() + " contacts loaded in: " + diffSeconds + "s || " +
(end - start) + " ms");
// Convert to ArrayList if you need an arraylist
ArrayList<Friend> mContacts = new ArrayList<>();
for (Map.Entry<Integer, Friend> friend : tempContacts.entrySet()) {
mContacts.add(friend.getValue());
}
Log.d(TAG, "ArrayList created from contacts");
// Do whatever you want to do with the loaded contacts
}

This function provides a list of contacts which contains an email address or a phone number. And the data we are getting is Name, Email and Photo uri of the contact.

So, we are querying everything from the ContactsContract.Data table and then merging the data based on the CONTACT_ID of the record.

This will give us the results pretty fast rather than querying by phone no and querying again for email address of that person. We get all the records in the single query instead of the nested queries which you may have found on some other stackoverflow questions or other sites. Because of this, performance is very good. You can now use the RecyclerView to display your records.

So, that's it. Hope you have now understood how things works. Let me know in the comments if you have any doubts.