Updated 5 days ago | GitHub

Loading Contacts with Content Providers

Overview

This guide covers reading the device’s contacts from the Contacts Provider, Android’s central content provider for contact data, and showing them in a list.

NOTE: The CursorLoader walkthrough in the second half of this page is deprecated. Loaders were deprecated in Android 9 (API level 28) in favor of a combination of ViewModel and LiveData. The current approach is to query the ContentResolver from a ViewModel on a background coroutine and expose the results to the UI as observable state, as shown below. For loading very large result sets a page at a time, use the Paging 3 library — see the Paging Library Guide.

Modern Approach: ViewModel + Coroutines

The recommended pattern queries ContactsContract through the ContentResolver on a background dispatcher inside a ViewModel, then exposes the results as a StateFlow the UI can collect. First, define a small model class:

data class Contact(val id: Long, val name: String?, val photoUri: String?)

Then perform the query inside a ViewModel so the loaded list survives configuration changes:

class ContactsViewModel(application: Application) : AndroidViewModel(application) {
    private val _contacts = MutableStateFlow<List<Contact>>(emptyList())
    val contacts: StateFlow<List<Contact>> = _contacts

    fun loadContacts() {
        viewModelScope.launch(Dispatchers.IO) {
            val projection = arrayOf(
                ContactsContract.Contacts._ID,
                ContactsContract.Contacts.DISPLAY_NAME,
                ContactsContract.Contacts.PHOTO_URI
            )
            val loaded = mutableListOf<Contact>()
            getApplication<Application>().contentResolver.query(
                ContactsContract.Contacts.CONTENT_URI, // URI
                projection,                            // projection fields
                null,                                  // the selection criteria
                null,                                  // the selection args
                ContactsContract.Contacts.DISPLAY_NAME // the sort order
            )?.use { cursor ->
                val idCol = cursor.getColumnIndexOrThrow(ContactsContract.Contacts._ID)
                val nameCol = cursor.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME)
                val photoCol = cursor.getColumnIndexOrThrow(ContactsContract.Contacts.PHOTO_URI)
                while (cursor.moveToNext()) {
                    loaded.add(
                        Contact(
                            id = cursor.getLong(idCol),
                            name = cursor.getString(nameCol),
                            photoUri = cursor.getString(photoCol)
                        )
                    )
                }
            }
            _contacts.value = loaded
        }
    }
}

Finally, collect the flow in your Activity or Fragment and submit the list to a RecyclerView adapter (a ListAdapter with DiffUtil works well here):

class SampleActivity : AppCompatActivity() {
    private val viewModel: ContactsViewModel by viewModels()
    private val contactsAdapter = ContactsAdapter() // your ListAdapter<Contact, ...> subclass

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_sample)
        findViewById<RecyclerView>(R.id.rvContacts).adapter = contactsAdapter
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.contacts.collect { contacts ->
                    contactsAdapter.submitList(contacts)
                }
            }
        }
        viewModel.loadContacts() // after READ_CONTACTS permission is granted
    }
}

Remember that READ_CONTACTS is a runtime permission (see the Permissions section below) — only call loadContacts() once the permission has been granted. For deeper queries (phone numbers, emails, per-contact details), see the official Retrieving a list of contacts training guide.

Historical Reference: CursorLoader and SimpleCursorAdapter

Deprecated: this section demonstrates the pre-API-28 LoaderManager / CursorLoader machinery and the ListView-based SimpleCursorAdapter. It is kept as a reference for maintaining older codebases; new code should use the ViewModel + coroutines approach above.

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:

  • ContentResolver - Provides access to the content models for a given application.
  • 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"/>

Note: The permissions model has changed starting in Marshmallow. If your targetSdkVersion >= 23 and you are running on a Marshmallow (or later) device, you may need to enable runtime permissions in order to get the READ_CONTACTS permission. You can also read more about the runtime permissions changes here.

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 AppCompatActivity {
    // ... 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,
            ContactsContract.Contacts.PHOTO_URI };
      	// View IDs which will have the respective column data inserted
        int[] uiBindTo = { R.id.tvName, R.id.ivImage };
        // Create the simple cursor adapter to use for our list
        // specifying the template to inflate (item_contact),
      	adapter = new SimpleCursorAdapter(
                  this, R.layout.item_contact,
                  null, uiBindFrom, uiBindTo,
                  0);
    }
}

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 AppCompatActivity {
    @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 AppCompatActivity {
   // ... 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,
                       ContactsContract.Contacts.PHOTO_URI };
    		// 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 AppCompatActivity {
    // ... existing code
    // Defines the id of the loader for later reference
    public static final int CONTACT_LOADER_ID = 78; // From docs: A unique identifier for this loader. Can be whatever you want.

    @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.

Contracts

There is yet to be a standard place to find what data is exposed by various Content Providers. Here is a list of known contracts defined for a few common ones.

  • Browser.BookmarkColumns (Docs)
  • CalendarContract (Docs)
  • ContactsContract (Docs)
  • UserDictionary (Docs)
  • MediaStore.Audio, MediaStore.Video (Docs)
  • DocumentsContract (Docs)
  • Settings (Docs)

Cursors and RecyclerView

With the modern approach above there is no need for a cursor-based adapter at all — the cursor is read into a plain List on a background coroutine and handed to a regular ListAdapter. If you are maintaining legacy code that keeps a live Cursor bound to the UI, note that RecyclerView has no built-in cursor-based adapters, but Arnaud Frugier wrote a great post providing the sample code for cursor-based RecyclerView adapters. To use cursors and loaders with RecyclerView, copy these files into your codebase:

These files will enable you to write cursor-based RecyclerView adapters in a style that is very similar to the approach taken with ListView.

References