page.title=Retrieving a List of Contacts trainingnavtop=true @jd:body
ContactsList.zip
This lesson shows you how to retrieve a list of contacts whose data matches all or part of a search string, using the following techniques:
Note: All the examples in this lesson use a {@link android.support.v4.content.CursorLoader} to retrieve data from the Contacts Provider. A {@link android.support.v4.content.CursorLoader} runs its query on a thread that's separate from the UI thread. This ensures that the query doesn't slow down UI response times and cause a poor user experience. For more information, see the Android training class Loading Data in the Background.
To do any type of search of the Contacts Provider, your app must have
{@link android.Manifest.permission#READ_CONTACTS READ_CONTACTS} permission.
To request this, add this
<uses-permission>
element to your manifest file as a child element of
<manifest>
:
<uses-permission android:name="android.permission.READ_CONTACTS" />
This technique tries to match a search string to the name of a contact or contacts in the Contact Provider's {@link android.provider.ContactsContract.Contacts} table. You usually want to display the results in a {@link android.widget.ListView}, to allow the user to choose among the matched contacts.
To display the search results in a {@link android.widget.ListView}, you need a main layout file
that defines the entire UI including the {@link android.widget.ListView}, and an item layout
file that defines one line of the {@link android.widget.ListView}. For example, you could create
the main layout file res/layout/contacts_list_view.xml
with
the following XML:
<?xml version="1.0" encoding="utf-8"?> <ListView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/list" android:layout_width="match_parent" android:layout_height="match_parent"/>
This XML uses the built-in Android {@link android.widget.ListView} widget {@link android.R.id#list android:id/list}.
Define the item layout file contacts_list_item.xml
with the following XML:
<?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/text1" android:layout_width="match_parent" android:layout_height="wrap_content" android:clickable="true"/>
This XML uses the built-in Android {@link android.widget.TextView} widget {@link android.R.id#text1 android:text1}.
Note: This lesson doesn't describe the UI for getting a search string from the user, because you may want to get the string indirectly. For example, you can give the user an option to search for contacts whose name matches a string in an incoming text message.
The two layout files you've written define a user interface that shows a {@link android.widget.ListView}. The next step is to write code that uses this UI to display a list of contacts.
To display the list of contacts, start by defining a {@link android.support.v4.app.Fragment} that's loaded by an {@link android.app.Activity}. Using a {@link android.support.v4.app.Fragment} is a more flexible technique, because you can use one {@link android.support.v4.app.Fragment} to display the list and a second {@link android.support.v4.app.Fragment} to display the details for a contact that the user chooses from the list. Using this approach, you can combine one of the techniques presented in this lesson with one from the lesson Retrieving Details for a Contact.
To learn how to use one or more {@link android.support.v4.app.Fragment} objects from an an {@link android.app.Activity}, read the training class Building a Dynamic UI with Fragments.
To help you write queries against the Contacts Provider, the Android framework provides a contracts class called {@link android.provider.ContactsContract}, which defines useful constants and methods for accessing the provider. When you use this class, you don't have to define your own constants for content URIs, table names, or columns. To use this class, include the following statement:
import android.provider.ContactsContract;
Since the code uses a {@link android.support.v4.content.CursorLoader} to retrieve data from the provider, you must specify that it implements the loader interface {@link android.support.v4.app.LoaderManager.LoaderCallbacks}. Also, to help detect which contact the user selects from the list of search results, implement the adapter interface {@link android.widget.AdapterView.OnItemClickListener}. For example:
... import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.widget.AdapterView; ... public class ContactsFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor>, AdapterView.OnItemClickListener {
Define global variables that are used in other parts of the code:
... /* * Defines an array that contains column names to move from * the Cursor to the ListView. */ @SuppressLint("InlinedApi") private final static String[] FROM_COLUMNS = { Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ? Contacts.DISPLAY_NAME_PRIMARY : Contacts.DISPLAY_NAME }; /* * Defines an array that contains resource ids for the layout views * that get the Cursor column contents. The id is pre-defined in * the Android framework, so it is prefaced with "android.R.id" */ private final static int[] TO_IDS = { android.R.id.text1 }; // Define global mutable variables // Define a ListView object ListView mContactsList; // Define variables for the contact the user selects // The contact's _ID value long mContactId; // The contact's LOOKUP_KEY String mContactKey; // A content URI for the selected contact Uri mContactUri; // An adapter that binds the result Cursor to the ListView private SimpleCursorAdapter mCursorAdapter; ...
Note: Since
{@link android.provider.ContactsContract.ContactNameColumns#DISPLAY_NAME_PRIMARY
Contacts.DISPLAY_NAME_PRIMARY} requires Android 3.0 (API version 11) or later, setting your
app's minSdkVersion
to 10 or below generates an Android Lint warning in
Android Studio. To turn off this warning, add the annotation
@SuppressLint("InlinedApi")
before the definition of FROM_COLUMNS
.
Initialize the {@link android.support.v4.app.Fragment}. Add the empty, public constructor required by the Android system, and inflate the {@link android.support.v4.app.Fragment} object's UI in the callback method {@link android.support.v4.app.Fragment#onCreateView onCreateView()}. For example:
// Empty public constructor, required by the system public ContactsFragment() {} // A UI Fragment must inflate its View @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the fragment layout return inflater.inflate(R.layout.contact_list_fragment, container, false); }
Set up the {@link android.support.v4.widget.SimpleCursorAdapter} that binds the results of the search to the {@link android.widget.ListView}. To get the {@link android.widget.ListView} object that displays the contacts, you need to call {@link android.app.Activity#findViewById Activity.findViewById()} using the parent activity of the {@link android.support.v4.app.Fragment}. Use the {@link android.content.Context} of the parent activity when you call {@link android.widget.AdapterView#setAdapter setAdapter()}. For example:
public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); ... // Gets the ListView from the View list of the parent activity mContactsList = (ListView) getActivity().findViewById(R.layout.contact_list_view); // Gets a CursorAdapter mCursorAdapter = new SimpleCursorAdapter( getActivity(), R.layout.contact_list_item, null, FROM_COLUMNS, TO_IDS, 0); // Sets the adapter for the ListView mContactsList.setAdapter(mCursorAdapter); }
When you display the results of a search, you usually want to allow the user to select a single contact for further processing. For example, when the user clicks a contact you can display the contact's address on a map. To provide this feature, you first defined the current {@link android.support.v4.app.Fragment} as the click listener by specifying that the class implements {@link android.widget.AdapterView.OnItemClickListener}, as shown in the section Define a Fragment that displays the list of contacts.
To continue setting up the listener, bind it to the {@link android.widget.ListView} by calling the method {@link android.widget.AdapterView#setOnItemClickListener setOnItemClickListener()} in {@link android.support.v4.app.Fragment#onActivityCreated onActivityCreated()}. For example:
public void onActivityCreated(Bundle savedInstanceState) { ... // Set the item click listener to be the current fragment. mContactsList.setOnItemClickListener(this); ... }
Since you specified that the current {@link android.support.v4.app.Fragment} is the {@link android.widget.AdapterView.OnItemClickListener OnItemClickListener} for the {@link android.widget.ListView}, you now need to implement its required method {@link android.widget.AdapterView.OnItemClickListener#onItemClick onItemClick()}, which handles the click event. This is described in a succeeding section.
Define a constant that contains the columns you want to return from your query. Each item in the {@link android.widget.ListView} displays the contact's display name, which contains the main form of the contact's name. In Android 3.0 (API version 11) and later, the name of this column is {@link android.provider.ContactsContract.ContactNameColumns#DISPLAY_NAME_PRIMARY Contacts.DISPLAY_NAME_PRIMARY}; in versions previous to that, its name is {@link android.provider.ContactsContract.ContactsColumns#DISPLAY_NAME Contacts.DISPLAY_NAME}.
The column {@link android.provider.BaseColumns#_ID Contacts._ID} is used by the {@link android.support.v4.widget.SimpleCursorAdapter} binding process. {@link android.provider.BaseColumns#_ID Contacts._ID} and {@link android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} are used together to construct a content URI for the contact the user selects.
... @SuppressLint("InlinedApi") private static final String[] PROJECTION = { Contacts._ID, Contacts.LOOKUP_KEY, Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ? Contacts.DISPLAY_NAME_PRIMARY : Contacts.DISPLAY_NAME };
To get data from an individual column in a {@link android.database.Cursor}, you need the column's index within the {@link android.database.Cursor}. You can define constants for the indexes of the {@link android.database.Cursor} columns, because the indexes are the same as the order of the column names in your projection. For example:
// The column index for the _ID column private static final int CONTACT_ID_INDEX = 0; // The column index for the LOOKUP_KEY column private static final int LOOKUP_KEY_INDEX = 1;
To specify the data you want, create a combination of text expressions and variables that tell the provider the data columns to search and the values to find.
For the text expression, define a constant that lists the search columns. Although this expression can contain values as well, the preferred practice is to represent the values with a "?" placeholder. During retrieval, the placeholder is replaced with values from an array. Using "?" as a placeholder ensures that the search specification is generated by binding rather than by SQL compilation. This practice eliminates the possibility of malicious SQL injection. For example:
// Defines the text expression @SuppressLint("InlinedApi") private static final String SELECTION = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ? Contacts.DISPLAY_NAME_PRIMARY + " LIKE ?" : Contacts.DISPLAY_NAME + " LIKE ?"; // Defines a variable for the search string private String mSearchString; // Defines the array to hold values that replace the ? private String[] mSelectionArgs = { mSearchString };
In a previous section, you set the item click listener for the {@link android.widget.ListView}. Now implement the action for the listener by defining the method {@link android.widget.AdapterView.OnItemClickListener#onItemClick AdapterView.OnItemClickListener.onItemClick()}:
@Override public void onItemClick( AdapterView<?> parent, View item, int position, long rowID) { // Get the Cursor Cursor cursor = parent.getAdapter().getCursor(); // Move to the selected contact cursor.moveToPosition(position); // Get the _ID value mContactId = getLong(CONTACT_ID_INDEX); // Get the selected LOOKUP KEY mContactKey = getString(CONTACT_KEY_INDEX); // Create the contact's content Uri mContactUri = Contacts.getLookupUri(mContactId, mContactKey); /* * You can use mContactUri as the content URI for retrieving * the details for a contact. */ }
Since you're using a {@link android.support.v4.content.CursorLoader} to retrieve data, you must initialize the background thread and other variables that control asynchronous retrieval. Do the initialization in {@link android.support.v4.app.Fragment#onActivityCreated onActivityCreated()}, which is invoked immediately before the {@link android.support.v4.app.Fragment} UI appears, as shown in the following example:
public class ContactsFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> { ... // Called just before the Fragment displays its UI @Override public void onActivityCreated(Bundle savedInstanceState) { // Always call the super method first super.onActivityCreated(savedInstanceState); ... // Initializes the loader getLoaderManager().initLoader(0, null, this);
Implement the method {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()}, which is called by the loader framework immediately after you call {@link android.support.v4.app.LoaderManager#initLoader initLoader()}.
In {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()}, set up the search string pattern. To make a string into a pattern, insert "%" (percent) characters to represent a sequence of zero or more characters, or "_" (underscore) characters to represent a single character, or both. For example, the pattern "%Jefferson%" would match both "Thomas Jefferson" and "Jefferson Davis".
Return a new {@link android.support.v4.content.CursorLoader} from the method. For the content URI, use {@link android.provider.ContactsContract.Contacts#CONTENT_URI Contacts.CONTENT_URI}. This URI refers to the entire table, as shown in the following example:
... @Override public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) { /* * Makes search string into pattern and * stores it in the selection array */ mSelectionArgs[0] = "%" + mSearchString + "%"; // Starts the query return new CursorLoader( getActivity(), Contacts.CONTENT_URI, PROJECTION, SELECTION, mSelectionArgs, null ); }
Implement the {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()} method. The loader framework calls {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()} when the Contacts Provider returns the results of the query. In this method, put the result {@link android.database.Cursor} in the {@link android.support.v4.widget.SimpleCursorAdapter}. This automatically updates the {@link android.widget.ListView} with the search results:
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { // Put the result Cursor in the adapter for the ListView mCursorAdapter.swapCursor(cursor); }
The method {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onLoaderReset onLoaderReset()} is invoked when the loader framework detects that the result {@link android.database.Cursor} contains stale data. Delete the {@link android.support.v4.widget.SimpleCursorAdapter} reference to the existing {@link android.database.Cursor}. If you don't, the loader framework will not recycle the {@link android.database.Cursor}, which causes a memory leak. For example:
@Override public void onLoaderReset(Loader<Cursor> loader) { // Delete the reference to the existing Cursor mCursorAdapter.swapCursor(null); }
You now have the key pieces of an app that matches a search string to contact names and returns the result in a {@link android.widget.ListView}. The user can click a contact name to select it. This triggers a listener, in which you can work further with the contact's data. For example, you can retrieve the contact's details. To learn how to do this, continue with the next lesson, Retrieving Details for a Contact.
To learn more about search user interfaces, read the API guide Creating a Search Interface.
The remaining sections in this lesson demonstrate other ways of finding contacts in the Contacts Provider.
This technique allows you to specify the type of data you want to match. Retrieving by name is a specific example of this type of query, but you can also do it for any of the types of detail data associated with a contact. For example, you can retrieve contacts that have a specific postal code; in this case, the search string has to match data stored in a postal code row.
To implement this type of retrieval, first implement the following code, as listed in previous sections:
Although you're retrieving data from a different table, the order of the columns in the projection is the same, so you can use the same indexes for the Cursor.
The following steps show you the additional code you need to match a search string to a particular type of detail data and display the results.
To search for a particular type of detail data, you have to know the custom MIME type value
for the data type. Each data type has a unique MIME type
value defined by a constant CONTENT_ITEM_TYPE
in the subclass of
{@link android.provider.ContactsContract.CommonDataKinds} associated with the data type.
The subclasses have names that indicate their data type; for example, the subclass for email
data is {@link android.provider.ContactsContract.CommonDataKinds.Email}, and the custom MIME
type for email data is defined by the constant
{@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_ITEM_TYPE
Email.CONTENT_ITEM_TYPE}.
Use the {@link android.provider.ContactsContract.Data} table for your search. All of the constants you need for your projection, selection clause, and sort order are defined in or inherited by this table.
To define a projection, choose one or more of the columns defined in {@link android.provider.ContactsContract.Data} or the classes from which it inherits. The Contacts Provider does an implicit join between {@link android.provider.ContactsContract.Data} and other tables before it returns rows. For example:
@SuppressLint("InlinedApi") private static final String[] PROJECTION = { /* * The detail data row ID. To make a ListView work, * this column is required. */ Data._ID, // The primary display name Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ? Data.DISPLAY_NAME_PRIMARY : Data.DISPLAY_NAME, // The contact's _ID, to construct a content URI Data.CONTACT_ID // The contact's LOOKUP_KEY, to construct a content URI Data.LOOKUP_KEY (a permanent link to the contact };
To search for a string within a particular type of data, construct a selection clause from the following:
CONTENT_ITEM_TYPE
in the
{@link android.provider.ContactsContract.CommonDataKinds} subclass. For example, the MIME
type value for email data is
{@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_ITEM_TYPE
Email.CONTENT_ITEM_TYPE}. Enclose the value in single quotes by concatenating a
"'
" (single quote) character to the start and end of the constant; otherwise,
the provider interprets the value as a variable name rather than as a string value.
You don't need to use a placeholder for this value, because you're using a constant
rather than a user-supplied value.
For example:
/* * Constructs search criteria from the search string * and email MIME type */ private static final String SELECTION = /* * Searches for an email address * that matches the search string */ Email.ADDRESS + " LIKE ? " + "AND " + /* * Searches for a MIME type that matches * the value of the constant * Email.CONTENT_ITEM_TYPE. Note the * single quotes surrounding Email.CONTENT_ITEM_TYPE. */ Data.MIMETYPE + " = '" + Email.CONTENT_ITEM_TYPE + "'";
Next, define variables to contain the selection argument:
String mSearchString; String[] mSelectionArgs = { "" };
Now that you've specified the data you want and how to find it, define a query in your implementation of {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()}. Return a new {@link android.support.v4.content.CursorLoader} from this method, using your projection, selection text expression, and selection array as arguments. For a content URI, use {@link android.provider.ContactsContract.Data#CONTENT_URI Data.CONTENT_URI}. For example:
@Override public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) { // OPTIONAL: Makes search string into pattern mSearchString = "%" + mSearchString + "%"; // Puts the search string into the selection criteria mSelectionArgs[0] = mSearchString; // Starts the query return new CursorLoader( getActivity(), Data.CONTENT_URI, PROJECTION, SELECTION, mSelectionArgs, null ); }
These code snippets are the basis of a simple reverse lookup based on a specific type of detail data. This is the best technique to use if your app focuses on a particular type of data, such as emails, and you want allow users to get the names associated with a piece of data.
Retrieving a contact based on any type of data returns contacts if any of their data matches a the search string, including name, email address, postal address, phone number, and so forth. This results in a broad set of search results. For example, if the search string is "Doe", then searching for any data type returns the contact "John Doe"; it also returns contacts who live on "Doe Street".
To implement this type of retrieval, first implement the following code, as listed in previous sections:
For this type of retrieval, you're using the same table you used in the section Match a Contact by Name and List the Results. Use the same column indexes as well.
The following steps show you the additional code you need to match a search string to any type of data and display the results.
Don't define the SELECTION
constants or the mSelectionArgs
variable.
These aren't used in this type of retrieval.
Implement the {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()} method, returning a new {@link android.support.v4.content.CursorLoader}. You don't need to convert the search string into a pattern, because the Contacts Provider does that automatically. Use {@link android.provider.ContactsContract.Contacts#CONTENT_FILTER_URI Contacts.CONTENT_FILTER_URI} as the base URI, and append your search string to it by calling {@link android.net.Uri#withAppendedPath Uri.withAppendedPath()}. Using this URI automatically triggers searching for any data type, as shown in the following example:
@Override public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) { /* * Appends the search string to the base URI. Always * encode search strings to ensure they're in proper * format. */ Uri contentUri = Uri.withAppendedPath( Contacts.CONTENT_FILTER_URI, Uri.encode(mSearchString)); // Starts the query return new CursorLoader( getActivity(), contentUri, PROJECTION, null, null, null ); }
These code snippets are the basis of an app that does a broad search of the Contacts Provider. The technique is useful for apps that want to implement functionality similar to the People app's contact list screen.