Working with the “ContactsContract” to query contacts in Android

When I was looking at the official example on the Google Android Developers site for accessing content providers in Android (http://developer.android.com/guide/topics/providers/content-providers.html) I found an outdated example to query contacts which is using deprecated fields in the Android API. As I’ve seen quite some developers who are still relying on that deprecated example to implement their functionality even when using the newer API levels I’ve decided to post an example which is using the new way suggested in the Android API.

Query Contacts AppTo demonstrate the features of the new API the example App is querying all available contacts on the phone and additionally commonly used information from the contacts content provider such as the name, phone numbers, email addresses and of course the photo.

The new approach using the ContactsContract was introduced with API level 5 (Android 2.0) and was further improved with API level 11 (Android 3.0), but I did not use any of the new features in this example so it’s compatible for any versions which are at least using API level 5.

You can browse or download the source code of the example App at our SVN repository at Google Code (http://code.google.com/p/android-contacts-contract-example/) or use svn to check the project out.

Feel free to reuse portions of this code as you wish.

As this post is written for beginners you might want to jump directly to a specific topic within this post:

Basic concept of Content Providers

Before we start let’s take a short look at the basic concept to access content providers. Content providers are providing data using a database like approach. The database of the content provider is always addressed by an unique URI e.g. “content://com.appsolut.example/exampleData”. To access a specific content provider, the first step is to create a query resulting in a Cursor which represents the returned data as an object with random access. The configuration of queries is straightforward and can be described in five steps:

  1. Identify the unique resource identifier (URI) of the desired content provider
  2. Generate a String array which is holding the names of the columns which you require from the database (e.g.  RawContacts.CONTACT_ID). This is called projection.
  3. If you want to filter the results using the query define a selection clause (e.g. to filter by contact ids: RawContacts.CONTACT_ID + “=?”). The ? operator acts as a parameter which is defined in the next step. This is called selection.
  4. Create another String array for all parameters which you’ve used in your selection clause. For the above example it could be something like new String[]{contactId}. If no parameters where used just ignore this step. This array is called selectionArgs.
  5. If you want to sort your results by table columns define a sort order like RawContacts.CONTACT_ID + ” ASC”, which will sort the results in ascending order using their contact ids. This string is called sortOrder.

Using the parameters from these five steps the query method can be called.

public final Cursor managedQuery(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)

Note: starting with API level 11 the managedQuery method will also be marked as deprecated but the new concept is quite similar.

Using the cursor rows can be selected and specific values of columns in this row can be returned using getters like getString or getInt.

For a more detailed description about content providers visit the documentation at http://developer.android.com/guide/topics/providers/content-providers.html – but don’t use the proposed way to access the contacts content provider as it is outdated and deprecated.

Querying contacts and further details of a contact

Enough basics, let’s query contacts and information related to these contacts. For that purpose the “android.provider.ContactsContract“ class and subpackages were introduced at API level 5.

Permission

The basic step before using the “ContactsContract” in your App is to add the required permission

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.appsolut.example.queryContacts" android:versionCode="1" android:versionName="1.0">
	<uses-permission android:name="android.permission.READ_CONTACTS" />
	…
</manifest>

to your projects manifest file. Otherwise your App will always crash when you access the content providers.

Querying a list of all available contacts

Now we can start accessing the “ContactsContract.RawContacts” (http://developer.android.com/reference/android/provider/ContactsContract.RawContacts.html) content provider to query all available contacts stored in the smartphone. This table represents every person as a single entry (one row). In this table an unique id is assigned to each person which is stored in the RawContacts.CONTACT_ID field (column). Unlike the default _ID column this id is used in the other tables as well, so we can later use it to query additional information about this person. To get access to the RawContacts content provider we first define a projection as shown here:

final String[] projection = new String[] {
RawContacts.CONTACT_ID,	// the contact id column
RawContacts.DELETED		// column if this contact is deleted
};

We are interested in the contact id column and the deleted column. The deleted column is important because there can be entries in the RawContacts table which have been deleted and therefore should not be displayed anymore. With the help of the projection we can now create the Cursor using a query.

final Cursor rawContacts = managedQuery(
RawContacts.CONTENT_URI,	// the URI for raw contact provider
projection
null,					// selection = null, retrieve all entries
null,					// selection is without parameters
null);					// do not order

Using this cursor we can iterate through all available contact ids. To do so we need to identify the index of the contact id and deleted column. We can use the getColumnIndex of the cursor object to get this index.

final int contactIdColumnIndex = rawContacts.getColumnIndex(RawContacts.CONTACT_ID);

final int deletedColumnIndex = rawContacts.getColumnIndex(RawContacts.DELETED);

To ensure that the cursor is pointing to the beginning and that there are valid entries we use the moveToFirst method, which will move the cursor to the first entry and return true if there are entries available. Now we can iterate over all entries using a while loop which checks the isAfterLast method of the cursor which will return true if the cursor is pointing to an non-existing entry.

if(rawContacts.moveToFirst()) {
	while(!rawContacts.isAfterLast()) {		// still a valid entry left?
		final int contactId = rawContacts.getInt(contactIdColumnIndex);
		final boolean deleted = (rawContacts.getInt(deletedColumnIndex) == 1);
		if(!deleted) {
			doSomethingWithAContactId(contactId));
		}
		rawContacts.moveToNext();			// move to the next entry
	}
}

Finally we can close the cursor to free resources:

rawContacts.close();

Querying basic information of a specific contact

Using a given contact id (for example from the previous part) we can access basic information about this person in the “ContactsContract.Contacts” (http://developer.android.com/reference/android/provider/ContactsContract.Contacts.html) table. In our example we will query the name of the contact as well as the photo id which is a reference to the photo entry in the data table and can thus later be used to query the photo as a bitmap. So first we define our projection for the columns DISPLAY_NAME and PHOTO_ID.

final String[] projection = new String[] {
	Contacts.DISPLAY_NAME,	// the name of the contact
	Contacts.PHOTO_ID		// the id of the column in the data table for the image
};

Using this selection we can create a cursor pointing to that specific contact. By using the selection and selectionArgs parameter of the query method we can filter the results of the query according to the given contact id (in our case the field contactId).

final Cursor contact = managedQuery(
				Contacts.CONTENT_URI,
				projection,
				Contacts._ID + "=?",	// filter entries on the basis of the contact id
				new String[]{String.valueOf(contactId)},	// the parameter to which the contact id column is compared to
				null);

Now we can retrieve the desired information.

if(contact.moveToFirst()) {
final String name = contact.getString(
	contact.getColumnIndex(Contacts.DISPLAY_NAME));
	final String photoId = contact.getString(
			contact.getColumnIndex(Contacts.PHOTO_ID));
	doSomethingWithAContactName(name);
	doSomethingWithAContactPhotoId(photoId);
}
contact.close();

Querying the photo of a contact

The contact photos are stored as binary large objects (blob) in the “ContactsContract.Data” table. In this table all kinds of data about a contact is stored so we need a given photo id to retrieve the correct entry (see Querying basic information of a specific contact). In our example we use the field photoId to represent this id. The column in which the blob is stored is defined in the “CommonDataKinds.Photo” class. Using this Photo.PHOTO column we can define our query as shown below.

final Cursor photo = managedQuery(
				Data.CONTENT_URI,
				new String[] {Photo.PHOTO},		// column for the blob
				Data._ID + "=?",				// select row by id
				new String[]{photoId},			// filter by photoId
				null);

If the contact has a photo linked to its entry, the cursor will return the photo blob. Using the “BitmapFactory” we can create a Bitmap using this blob.

if(photo.moveToFirst()) {
	byte[] photoBlob = photo.getBlob(
			photo.getColumnIndex(Photo.PHOTO));
	final Bitmap photoBitmap = BitmapFactory.decodeByteArray(
			photoBlob, 0, photoBlob.length);
	doSomethingWithAContactPhoto(photoBitmap);
}
photo.close();

Querying all phone numbers of a contact

The phone numbers are stored in the “ContactsContract.Data” table. Every number is represented by one entry in this table. To access the columns used for phone numbers we can use the definitions in the “ContactsContract.CommonDataKinds.Phone” class. There are three different columns available: number, type and label. The type column is used to define the type of the number e.g. work, home or other. If the type other is defined the label column can be used to get the defined name of this type. In our example we will just look at the types which are defined so our projection does not contain the label column.

final String[] projection = new String[] {
		Phone.NUMBER,
		Phone.TYPE,
};

As an URI for the phone number entries we can use the “Phone.CONTENT_URI” URI which filters the data table according to the media type of phone numbers. Because we only want phone numbers of a specific contact we filter the results on the basis of the given contactId field.

final Cursor phone = managedQuery(
				Phone.CONTENT_URI,
				projection,
				Data.CONTACT_ID + "=?",
				new String[]{String.valueOf(contactId)},
				null);

Because there can be multiple entries we use a while loop to iterate over this cursor. To get a human readable version of the phone number type we use the getTypeLabelResource method to get the resource id of the label for a specific type.

if(phone.moveToFirst()) {
	final int contactNumberColumnIndex = phone.getColumnIndex(Phone.NUMBER);
	final int contactTypeColumnIndex = phone.getColumnIndex(Phone.TYPE);

	while(!phone.isAfterLast()) {
		final String number = phone.getString(contactNumberColumnIndex);
		final int type = phone.getInt(contactTypeColumnIndex);
		final int typeLabelResource = Phone.getTypeLabelResource(type);
		doSomethingWithAContactPhoneNumber(number, typeLabelResource);
		phone.moveToNext();
	}

}
phone.close();

Querying all email addresses of a contact

The email addresses of a contact are also stored in the “ContactsContract.Data” table. To retrieve the desired information we use the defined column names and URI in the “CommonDataKinds.Email” class. With API level 11 an extra field was added to this class to represent the address of the email but as this example is designed for API level 5+ we use the old field.

final String[] projection = new String[] {
		Email.DATA,			// use Email.ADDRESS for API-Level 11+
		Email.TYPE
};

The type of the email address is implemented like the type of phone numbers and using the getTypeLabelResource method we can retrieve a human readable label resource id of the type.  So now we can create our cursor to retrieve all available email addresses.

final Cursor email = managedQuery(
		Email.CONTENT_URI,
		projection,
		Data.CONTACT_ID + "=?",
		new String[]{String.valueOf(contactId)},
		null);

Just like we did process the results of the phone query we can iterate over the email cursor to extract every address.

if(email.moveToFirst()) {
	final int contactEmailColumnIndex = email.getColumnIndex(Email.DATA);
	final int contactTypeColumnIndex = email.getColumnIndex(Email.TYPE);

	while(!email.isAfterLast()) {
		final String address = email.getString(contactEmailColumnIndex);
		final int type = email.getInt(contactTypeColumnIndex);
		final int typeLabelResource = Email.getTypeLabelResource(type);
		doSomethingWithAContactEmailAddress(address, typeLabelResource);
		email.moveToNext();
	}

}
email.close();

Summary and Look-out

The presented ways are implemented in the example App which will query the details and display the information in a GUI. Using this App you can easily try the functions out. Other information such as the address, instant messenger, notes, etc. can easily be queried analogous to the way shown above. For further data fields just look at the classes in the “android.provider.ContactsContract.CommonDataKinds” package.

Please note that the example App is not written with the best performance in mind but to show a clear and easy understandable way to access information about contacts.

by Kevin Kratzer

 

Tags: , , , , , , , , , , , , , , , , , ,

20 comments

  1. Very good post. Just the way i wanted.
    Thank you!

  2. Tnks a lot man !
    cheers

  3. Very helpful…But this is not working for Honeycomb. It is crashing. The error is :
    ” ERROR/AndroidRuntime(557): Caused by: java.lang.IllegalStateException: trying to requery an already closed cursor”

    Can anybody please tell me what is the solution for this above problem in android 3.0?

    • Hey Prabhat!

      I tried to reproduce your exception but I was not able to do so. For me it works without any problems in a honeycomb emulator: http://goo.gl/2WVjY

      Maybe you can be more specific about your environment and the conditions in which this exception occurs.

  4. Prabhat, if you’re targetting HONEYCOMB, don’t use managedQuery because that’s already deprecated. Instead, use cursorLoader which takes the same argument. Refer to http://developer.android.com/reference/android/content/CursorLoader.html

  5. This is really good post . But I want to know how can I update phone’s Contacts data especially Contacts photo.
    Please reply asap

  6. Congrats for very good article
    Can you please provide an example of reading contacts using the new CursorLoader class.

    Regards

    Khurram

    • Thank you!

      I will try to create an additional post using the new classes which are now available but at the moment I’m quit busy so it might take some time.

      Greetings, Kevin

  7. fine, but
    can u tell me how to provide mulitiple selection like phne nubers home, mobile,emails like how mail, work emial at a time

  8. you can just change the selection in the way you need it instead of e.g using ‘Data.CONTACT_ID + “=?”‘ you could use something like ‘Phone.TYPE + “= ?”‘ where the selection args could be new String[]{String.valueOf(Phone.TYPE_HOME)}

    just try it out by yourself you will learn it better by doing it yourself. If you are having problems with the selections or projections just refer to an introduction into SQL which kinda works the same way.

  9. Thank you for the great source. Thats exactly What I needed. There is only one thing extra though. I tried to get rid of but couldnt. I hope you can direct me somehow. I do not want to use spinner at all. I just would like to bind all the contacts straight in to listview. is that possbile?

    Thank you.

  10. Yes that is possible, you just have to define a custom adapter for the ListView which will display the wanted information in a format which you desire. For more information you can take a look at the following two posts (especially the first one):
    http://app-solut.com/2011/03/using-custom-layouts-for-spinner-or-listview-entries-in-android/

    http://app-solut.com/2011/06/a-generic-listview-and-spinner-adapter-for-java-collections/

  11. Thank Your!

    This code is working in the emulator , but not working in the mobile device, I am using HTC Wildfire which has the Android OS version 2.3.3 for app testing, instead of managedQuery() ,I used getContentResolver().query(), still not working.. please help me

    • Hey,

      sorry I’m not able to tell you why this does not work on your phone (I don’t own a HTC phone at all). Maybe HTC implemented something differently. In a 2.3.3 emulator this code works just fine.

      Maybe someone else knows why the code does not run on that phone. You could also try to look at the HTC developer site http://htcdev.com/

  12. hi friends iam venugopal ,android beginer,

    please answer to me this question “how to get contact name to a particular phone number”
    iam strugling a lot .

    please help me in this.

    • Hey,

      just as I’ve stated in a previous comment you can change your selection and projection parameter in the way you need it. It’s pretty much just like SQL so if you are having trouble with the format of these parameters look up some basic SQL syntax.

      For your task you can modify the provided query for “Querying all phone numbers of a contact” so that the projection returns the Data.CONTACT_ID which identifies a contact uniquely. You can then just reuse the query stated at “Querying basic information of a specific contact” with that contact id.

  13. hey! first of all great tutorial! well my query is, is it possible to retrieve the names and phone numbers of contacts based on a substring? what im effectively asking is is it possible to implement a search function using the managed query?
    example if i need to search the contacts for a name kevin whose name is stored as akevinb and also there is another contact with the name aakevinb.. i need to retrieve both of these along with their respective phone numbers.. kindly guide me in this regard..

    thanks

    • Hey,

      this can be done by the SQLite like operator. It supports wildcards _ for a single character and % for multiple characters. You can see http://www.w3schools.com/sql/sql_like.asp and http://www.sqlite.org/lang_expr.html#like for more information.

      As I was also interested if this works I’ve implemented such a method:

      private void searchContacts(String searchName) {
      final String[] projection = new String[] {
      Contacts.DISPLAY_NAME,
      };

      final Cursor contact = managedQuery(
      Contacts.CONTENT_URI,
      projection,
      Contacts.DISPLAY_NAME + ” like ?”,
      new String[]{ “%” + searchName + “%”},
      null);

      if(contact.moveToFirst()) {
      while(!contact.isAfterLast()) {
      final String name = contact.getString(
      contact.getColumnIndex(Contacts.DISPLAY_NAME));
      Log.d(getClass().getCanonicalName(), “Searched for name: + ” + searchName + “, found: ” + name);
      contact.moveToNext();
      }
      }
      contact.close();
      }

      For my example setup this will produce the following output:

      D/com.appsolut.example.queryContacts.MainActivity( 382): Searched for name: + kevin, found: Akevin
      D/com.appsolut.example.queryContacts.MainActivity( 382): Searched for name: + kevin, found: kevin
      D/com.appsolut.example.queryContacts.MainActivity( 382): Searched for name: + kevin, found: Akevinb
      D/com.appsolut.example.queryContacts.MainActivity( 382): Searched for name: + kevin, found: Aaakevinbbbbb

      I hope this helps!

  14. Any code snippets on how to display the notes for the contacts?