Skip to content

Loading Contacts with Content Providers

Roger Hu edited this page Feb 27, 2015 · 53 revisions

Overview

There are two approaches to accessing data from content providers:

  1. Use the LoaderManager to execute query and bind cursor result to list using SimpleCursorAdapter
  2. Execute CursorLoader, iterate result cursor and construct ArrayList of objects

The two different methods are outlined below. In first approach, the loader manager is used to fetch the cursor asynchronously and the cursor is loaded directly into a SimpleCursorAdapter. In the the second, the content provider data for contacts is loaded synchronously and the cursor is iterated manually into an ArrayList<Contact> objects.

Using CursorLoader and SimpleCursorAdapter

In this example, we load the data directly from the ContentProvider using a CursorLoader and then plugging the resulting dataset directly into a SimpleCursorAdapter.

Let's define a few terms used below so we understand how this example fits together:

  • CursorLoader - A loader object that queries a ContentResolver for data and returns a Cursor.
  • Cursor - Provides random read-write access to the result set returned by the CursorLoader.
  • LoaderManager - Manages background loading operations such as async querying with CursorLoader.
  • CursorAdapter - Adapter that exposes data from a Cursor source to a ListView widget.

These four concepts are the underlying pieces to loading data through a content provider.

Permissions

First, setup permissions in the manifest:

<uses-permission android:name="android.permission.READ_CONTACTS"/>

Constructing the SimpleCursorAdapter

The SimpleCursorAdapter is an adapter that binds a ListView to a Cursor dataset displaying the result set as rows in the list. We can create the adapter before receiving the cursor by constructing as follows:

public class SampleActivity extends FragmentActivity {
    // ... existing code ...
    private SimpleCursorAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
       // ...
       setupCursorAdapter();
    }
    
    // Create simple cursor adapter to connect the cursor dataset we load with a listview
    private void setupCursorAdapter() {
        // Column data from cursor to bind views from	
      	String[] uiBindFrom = { ContactsContract.Contacts.DISPLAY_NAME };
      	// View IDs which will have the respective column data inserted
        int[] uiBindTo = { R.id.tvName };
        // Create the simple cursor adapter to use for our list
        // specifying the template to inflate (item_contact),
        // Fields to bind from and to and mark the adapter as observing for changes
      	adapter = new SimpleCursorAdapter(
                  this, R.layout.item_contact,
                  null, uiBindFrom, uiBindTo,
                  CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
    }
}

Note that if you want to use a more complex custom layout, you should construct a custom CursorAdapter to replace the SimpleCursorAdapter shown above.

Connecting the ListView

Once we've defined our cursor adapter, we can add a ListView to our activity called R.id.lvContacts which will contain the list of contacts loaded from the content provider. Once we've defined the ListView in our layout XML, we can bind the list to our adapter:

public class SampleActivity extends FragmentActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
       // ...
       setupCursorAdapter();
       // Find list and bind to adapter
       ListView lvContacts = (ListView) findViewById(R.id.lvContacts);
       lvContacts.setAdapter(adapter);
    }
}

Now, once we've loaded the Cursor from the Contacts ContentProvider, we can plug the dataset into the adapter and the list will automatically populate accordingly.

Using CursorLoader to Query the ContentProvider

Whenever we want to load data from a registered ContactProvider, we want to do the query asynchronously in order to avoid blocking the UI. Easiest way to execute this query is using a CursorLoader which runs an asynchronous query in the background against a ContentProvider, and returns the results to our Activity.

To execute the request to our contacts provider, we need to define the callbacks that the loader will use to create the request and handle the results. These callbacks are of type LoaderCallbacks and can be defined as follows:

public class SampleActivity extends FragmentActivity {
   // ... existing code

    // Defines the asynchronous callback for the contacts data loader
    private LoaderManager.LoaderCallbacks<Cursor> contactsLoader = 
        new LoaderManager.LoaderCallbacks<Cursor>() {
    	// Create and return the actual cursor loader for the contacts data
    	@Override
    	public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    		// Define the columns to retrieve
    		String[] projectionFields =  new String[] { ContactsContract.Contacts._ID, 
    	               ContactsContract.Contacts.DISPLAY_NAME };
    		// Construct the loader
    		CursorLoader cursorLoader = new CursorLoader(SampleActivity.this,
    				ContactsContract.Contacts.CONTENT_URI, // URI
    				projectionFields,  // projection fields
    				null, // the selection criteria
    				null, // the selection args
    				null // the sort order
    		);
    		// Return the loader for use
    		return cursorLoader;
    	}

    	// When the system finishes retrieving the Cursor through the CursorLoader, 
        // a call to the onLoadFinished() method takes place. 
    	@Override
    	public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
    		// The swapCursor() method assigns the new Cursor to the adapter
    		adapter.swapCursor(cursor);  
    	}

    	// This method is triggered when the loader is being reset 
        // and the loader data is no longer available. Called if the data 
        // in the provider changes and the Cursor becomes stale.
    	@Override
    	public void onLoaderReset(Loader<Cursor> loader) {
    		// Clear the Cursor we were using with another call to the swapCursor()
    		adapter.swapCursor(null);
    	}
    };
}

Now when a result comes back to the callback defined, the adapter will be bound to the cursor. With the loader callbacks specified, we can now setup our loader and execute the asynchronous request to the content provider:

public class SampleActivity extends FragmentActivity {
    // ... existing code
    // Defines the id of the loader for later reference
    public static final int CONTACT_LOADER_ID = 78;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // Bind adapter to list
        setupCursorAdapter();
        // Initialize the loader with a special ID and the defined callbacks from above
        getSupportLoaderManager().initLoader(CONTACT_LOADER_ID, 
            new Bundle(), contactsLoader);
    }
}

Now we have completed the process of loading the contacts into our list using a CursorLoader querying the ContactProvider and loading the resulting Cursor into the CursorAdapter. We should now see the list of the names of our contacts.

Loading Data From CursorLoader into Java Objects

The following guide walks step by step through loading contacts from the phone using Content Providers. See the full sample here for the source code.

Note that this is loading the content synchronously from the CursorLoader which should be avoided. This code parses the contacts with their numbers and emails into an ArrayList.

Permissions

First, setup permissions in the manifest:

<uses-permission android:name="android.permission.READ_CONTACTS"/>

Data Models

Next, you should create classes that will represent our Contact and his information. Start with Contact.java:

public class Contact {
	public String id;
	public String name;
	public ArrayList<ContactEmail> emails;
	public ArrayList<ContactPhone> numbers;

	public Contact(String id, String name) {
		this.id = id;
		this.name = name;
		this.emails = new ArrayList<ContactEmail>();
		this.numbers = new ArrayList<ContactPhone>();
	}

	public void addEmail(String address, String type) {
		emails.add(new ContactEmail(address, type));
	}

	public void addNumber(String number, String type) {
		numbers.add(new ContactPhone(number, type));
	}
}

and then the ContactPhone.java for the numbers:

public class ContactPhone {
	public String number;
	public String type;

	public ContactPhone(String number, String type) {
		this.number = number;
		this.type = type;
	}
}

and then the ContactEmail.java for the emails:

public class ContactEmail {
	public String address;
	public String type;

	public ContactEmail(String address, String type) {
		this.address = address;
		this.type = type;
	}
}

Fetcher

Now we need to write the code that actually queries the content provider and builds our Contact models up based on the queries to the provider:

// new ContactFetcher(this).fetchAll();
public class ContactFetcher {
	private Context context;

	public ContactFetcher(Context c) {
		this.context = c;
	}

	public ArrayList<Contact> fetchAll() {
		ArrayList<Contact> listContacts = new ArrayList<Contact>();
		CursorLoader cursorLoader = new CursorLoader(context, 
				ContactsContract.Contacts.CONTENT_URI, // uri
				null, // the columns to retrieve (all)
				null, // the selection criteria (none)
				null, // the selection args (none)
				null // the sort order (default)
		);
                // This should probably be run from an AsyncTask
                // loadInBackground() is actually a synchronous call to query the Content Provider
		Cursor c = cursorLoader.loadInBackground();
		if (c.moveToFirst()) {
			do {
			    Contact contact = loadContactData(c);
			    listContacts.add(contact);
			} while (c.moveToNext());
		}
		c.close();
		return listContacts;
	}

	private Contact loadContactData(Cursor c) {
		// Get Contact ID
		int idIndex = c.getColumnIndex(ContactsContract.Contacts._ID);
		String contactId = c.getString(idIndex);
		// Get Contact Name
		int nameIndex = c.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
		String contactDisplayName = c.getString(nameIndex);
		Contact contact = new Contact(contactId, contactDisplayName);
		fetchContactNumbers(c, contact);
		fetchContactEmails(c, contact);
		return contact;
	}

        
	public void fetchContactNumbers(Cursor cursor, Contact contact) {
		// Get numbers
		final String[] numberProjection = new String[] { Phone.NUMBER, Phone.TYPE, };
		Cursor phone = new CursorLoader(context, Phone.CONTENT_URI, numberProjection,
				Phone.CONTACT_ID + "= ?", 
				new String[] { String.valueOf(contact.id) },
				null).loadInBackground();

		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);
				String customLabel = "Custom";
				CharSequence phoneType = 
                                   ContactsContract.CommonDataKinds.Phone.getTypeLabel(
						context.getResources(), type, customLabel);
				contact.addNumber(number, phoneType.toString());
				phone.moveToNext();
			}

		}
		phone.close();
	}

	public void fetchContactEmails(Cursor cursor, Contact contact) {
		// Get email
		final String[] emailProjection = new String[] { Email.DATA, Email.TYPE };

		Cursor email = new CursorLoader(context, Email.CONTENT_URI, emailProjection,
				Email.CONTACT_ID + "= ?", 
				new String[] { String.valueOf(contact.id) },
				null).loadInBackground();

		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);
				String customLabel = "Custom";
				CharSequence emailType = 
                                    ContactsContract.CommonDataKinds.Email.getTypeLabel(
						context.getResources(), type, customLabel);
				contact.addEmail(address, emailType.toString());
				email.moveToNext();
			}

		}

		email.close();
	}
}

Activity

Now we want to populate the data into a ListView. First, the custom adapter:

public class ContactsAdapter extends ArrayAdapter<Contact> {

	public ContactsAdapter(Context context, ArrayList<Contact> contacts) {
		super(context, 0, contacts);
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		// Get the data item
		Contact contact = getItem(position);
		// Check if an existing view is being reused, otherwise inflate the view
		View view = convertView; 
		if (view == null) {
			LayoutInflater inflater = LayoutInflater.from(getContext());
			view = inflater.inflate(R.layout.adapter_contact_item, parent, false);
		}
		// Populate the data into the template view using the data object
		TextView tvName = (TextView) view.findViewById(R.id.tvName);
		TextView tvEmail = (TextView) view.findViewById(R.id.tvEmail);
		TextView tvPhone = (TextView) view.findViewById(R.id.tvPhone);
		tvName.setText(contact.name);
		tvEmail.setText("");
		tvPhone.setText("");
		if (contact.emails.size() > 0 && contact.emails.get(0) != null) {
			tvEmail.setText(contact.emails.get(0).address);
		}
		if (contact.numbers.size() > 0 && contact.numbers.get(0) != null) {
			tvPhone.setText(contact.numbers.get(0).number);
		}
		return view;
	}

}

and creating the adapter item xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:padding="3dp"
    android:layout_height="match_parent" >

    <TextView
        android:id="@+id/tvName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:text="Bob Marley"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/tvPhone"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@+id/tvName"
        android:text="(567) 789-5889"
        android:textAppearance="?android:attr/textAppearanceMedium" />

    <TextView
        android:id="@+id/tvEmail"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="#575757"
        android:layout_alignBaseline="@+id/tvPhone"
        android:layout_alignBottom="@+id/tvPhone"
        android:layout_alignParentRight="true"
        android:layout_toRightOf="@+id/tvPhone"
        android:paddingLeft="5dp"
        android:text="bob@fake.com"
        android:textAppearance="?android:attr/textAppearanceMedium" />

</RelativeLayout>

and then hooking up the ListView in the activity:

public class MainActivity extends Activity {
	private ArrayList<Contact> listContacts;
	private ListView lvContacts;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		listContacts = new ContactFetcher(this).fetchAll();
		lvContacts = (ListView) findViewById(R.id.lvContacts);
		ContactsAdapter adapterContacts = new ContactsAdapter(this, listContacts);
		lvContacts.setAdapter(adapterContacts);
	}
}

And now we should see the list of phone contacts!

Screen

References

Finding these guides helpful?

We need help from the broader community to improve these guides, add new topics and keep the topics up-to-date. See our contribution guidelines here and our topic issues list for great ways to help out.

Check these same guides through our standalone viewer for a better browsing experience and an improved search. Follow us on twitter @codepath for access to more useful Android development resources.

Clone this wiki locally