Cómo obtener una lista de contactos

En esta lección, se muestra cómo recuperar una lista de contactos cuyos datos coinciden con toda o parte de una cadena de búsqueda, utilizando las siguientes técnicas:

Hacer coincidir con los nombres de contactos
Recupera una lista de contactos que coinciden con la cadena de búsqueda en todo o parte de los datos del nombre del contacto. El proveedor de contactos permite varias instancias del mismo nombre, por lo que esta técnica puede devolver una lista de coincidencias.
Hacer coincidir con un tipo específico de datos, como un número de teléfono
Recupera una lista de contactos haciendo coincidir la cadena de búsqueda con un tipo particular de datos de detalles, como una dirección de correo electrónico. Por ejemplo, esta técnica te permite enumerar todos los contactos cuya dirección de correo electrónico coincide con la cadena de búsqueda.
Hacer coincidir con cualquier tipo de datos
Recupera una lista de contactos haciendo coincidir la cadena de búsqueda con cualquier tipo de datos de detalle, incluidos el nombre, el número de teléfono, la dirección, la dirección de correo electrónico, etcétera. Por ejemplo, esta técnica te permite aceptar cualquier tipo de datos para una cadena de búsqueda y, luego, enumerar los contactos para los que los datos coinciden con la cadena.

Nota: Todos los ejemplos de esta lección usan un CursorLoader para recuperar datos del proveedor de contactos. Un CursorLoader ejecuta su consulta en un subproceso independiente del subproceso de IU. Esto garantiza que la búsqueda no ralentice los tiempos de respuesta de la IU y no provoque una mala experiencia del usuario. Para obtener más información, consulta la clase de capacitación de Android Cómo cargar datos en segundo plano.

Cómo solicitar permiso para leer datos del proveedor

Para realizar cualquier tipo de búsqueda en el proveedor de contactos, tu app debe tener el permiso READ_CONTACTS. Para solicitarlo, agrega este elemento <uses-permission> a tu archivo de manifiesto como elemento secundario de <manifest>:

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

Cómo hacer coincidir un contacto por nombre y enumerar los resultados

Esta técnica intenta hacer coincidir una cadena de búsqueda con el nombre de uno o más contactos en la tabla ContactsContract.Contacts del proveedor de contactos. Por lo general, querrás mostrar los resultados en un ListView para permitir que el usuario elija entre los contactos que coinciden.

Cómo definir diseños de elementos y ListView

Para mostrar los resultados de la búsqueda en un ListView, necesitas un archivo de diseño principal que defina toda la IU, incluido el ListView, y un archivo de diseño de elementos que defina una línea del ListView. Por ejemplo, podrías crear el archivo de diseño principal res/layout/contacts_list_view.xml con el siguiente código 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"/>

Este XML usa el widget integrado de Android ListView android:id/list.

Define el archivo de diseño del elemento contacts_list_item.xml con el siguiente 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"/>

Este XML usa el widget integrado de Android TextView android:text1.

Nota: En esta lección, no se describe la IU para obtener una cadena de búsqueda del usuario, ya que es posible que quieras obtenerla de forma indirecta. Por ejemplo, puedes darle al usuario la opción de buscar contactos cuyo nombre coincida con una cadena en un mensaje de texto entrante.

Los dos archivos de diseño que escribiste definen una interfaz de usuario que muestra un ListView. El siguiente paso es escribir código que use esta IU para mostrar una lista de contactos.

Cómo definir un fragmento que muestre la lista de contactos

Para mostrar la lista de contactos, primero define un Fragment que se cargue con un Activity. Usar un Fragment es una técnica más flexible, ya que puedes usar un Fragment para mostrar la lista y un segundo Fragment para mostrar los detalles de un contacto que el usuario elija de la lista. Con este enfoque, puedes combinar una de las técnicas que se presentan en esta lección con una de la lección Cómo recuperar detalles de un contacto.

Para aprender a usar uno o más objetos Fragment de una Activity, lee la clase de capacitación Cómo compilar una IU dinámica con fragmentos.

Para ayudarte a escribir consultas en el proveedor de contactos, el framework de Android proporciona una clase de contratos llamada ContactsContract, que define constantes y métodos útiles para acceder al proveedor. Cuando usas esta clase, no tienes que definir tus propias constantes para los URI de contenido, los nombres de tablas ni las columnas. Para usar esta clase, incluye la siguiente sentencia:

Kotlin

import android.provider.ContactsContract

Java

import android.provider.ContactsContract;

Como el código usa un CursorLoader para recuperar datos del proveedor, debes especificar que implementa la interfaz del cargador LoaderManager.LoaderCallbacks. Además, para ayudar a detectar qué contacto selecciona el usuario de la lista de resultados de la búsqueda, implementa la interfaz del adaptador AdapterView.OnItemClickListener. Por ejemplo:

Kotlin

...
import android.support.v4.app.Fragment
import android.support.v4.app.LoaderManager
import android.widget.AdapterView
...
class ContactsFragment :
        Fragment(),
        LoaderManager.LoaderCallbacks<Cursor>,
        AdapterView.OnItemClickListener {

Java

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

Cómo definir variables globales

Define variables globales que se usan en otras partes del código:

Kotlin

...
/*
 * Defines an array that contains column names to move from
 * the Cursor to the ListView.
 */
@SuppressLint("InlinedApi")
private val FROM_COLUMNS: Array<String> = arrayOf(
        if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)) {
            ContactsContract.Contacts.DISPLAY_NAME_PRIMARY
        } else {
            ContactsContract.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 val TO_IDS: IntArray = intArrayOf(android.R.id.text1)
...
class ContactsFragment :
        Fragment(),
        LoaderManager.LoaderCallbacks<Cursor>,
        AdapterView.OnItemClickListener {
    ...
    // Define global mutable variables
    // Define a ListView object
    lateinit var contactsList: ListView
    // Define variables for the contact the user selects
    // The contact's _ID value
    var contactId: Long = 0
    // The contact's LOOKUP_KEY
    var contactKey: String? = null
    // A content URI for the selected contact
    var contactUri: Uri? = null
    // An adapter that binds the result Cursor to the ListView
    private val cursorAdapter: SimpleCursorAdapter? = null

Java

    ...
    /*
     * 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 ?
                    ContactsContract.Contacts.DISPLAY_NAME_PRIMARY :
                    ContactsContract.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 contactsList;
    // Define variables for the contact the user selects
    // The contact's _ID value
    long contactId;
    // The contact's LOOKUP_KEY
    String contactKey;
    // A content URI for the selected contact
    Uri contactUri;
    // An adapter that binds the result Cursor to the ListView
    private SimpleCursorAdapter cursorAdapter;
    ...

Nota: Dado que Contacts.DISPLAY_NAME_PRIMARY requiere Android 3.0 (versión de API 11) o versiones posteriores, establecer el valor de minSdkVersion de tu app en 10 o menos genera una advertencia de Android Lint en Android Studio. Para desactivar esta advertencia, agrega la anotación @SuppressLint("InlinedApi") antes de la definición de FROM_COLUMNS.

Cómo inicializar el fragmento

Inicializa el objeto Fragment. Agrega el constructor público vacío que requiere el sistema Android y, luego, infla la IU del objeto Fragment en el método de devolución de llamada onCreateView(). Por ejemplo:

Kotlin

    // A UI Fragment must inflate its View
    override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
    ): View? {
        // Inflate the fragment layout
        return inflater.inflate(R.layout.contact_list_fragment, container, false)
    }

Java

    // 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);
    }

Cómo configurar el CursorAdapter para el elemento ListView

Configura el SimpleCursorAdapter que vincula los resultados de la búsqueda con el ListView. Para obtener el objeto ListView que muestra los contactos, debes llamar a Activity.findViewById() con la actividad superior de Fragment. Usa el Context de la actividad principal cuando llames a setAdapter(). Por ejemplo:

Kotlin

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        ...
        // Gets the ListView from the View list of the parent activity
        activity?.also {
            contactsList = it.findViewById<ListView>(R.id.contact_list_view)
            // Gets a CursorAdapter
            cursorAdapter = SimpleCursorAdapter(
                    it,
                    R.layout.contact_list_item,
                    null,
                    FROM_COLUMNS, TO_IDS,
                    0
            )
            // Sets the adapter for the ListView
            contactsList.adapter = cursorAdapter
        }
    }

Java

    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        ...
        // Gets the ListView from the View list of the parent activity
        contactsList =
            (ListView) getActivity().findViewById(R.layout.contact_list_view);
        // Gets a CursorAdapter
        cursorAdapter = new SimpleCursorAdapter(
                getActivity(),
                R.layout.contact_list_item,
                null,
                FROM_COLUMNS, TO_IDS,
                0);
        // Sets the adapter for the ListView
        contactsList.setAdapter(cursorAdapter);
    }

Cómo configurar el objeto de escucha del contacto seleccionado

Cuando muestras los resultados de una búsqueda, generalmente quieres permitir que el usuario seleccione un solo contacto para su procesamiento posterior. Por ejemplo, cuando el usuario hace clic en un contacto, puedes mostrar su dirección en un mapa. Para proporcionar esta función, primero definiste el Fragment actual como el objeto de escucha de clics especificando que la clase implementa AdapterView.OnItemClickListener, como se muestra en la sección Define un fragmento que muestre la lista de contactos.

Para continuar con la configuración del objeto de escucha, vincúlalo a ListView llamando al método setOnItemClickListener() en onActivityCreated(). Por ejemplo:

Kotlin

    fun onActivityCreated(savedInstanceState:Bundle) {
        ...
        // Set the item click listener to be the current fragment.
        contactsList.onItemClickListener = this
        ...
    }

Java

    public void onActivityCreated(Bundle savedInstanceState) {
        ...
        // Set the item click listener to be the current fragment.
        contactsList.setOnItemClickListener(this);
        ...
    }

Como especificaste que el Fragment actual es el OnItemClickListener para el ListView, ahora debes implementar su método onItemClick() obligatorio, que controla el evento de clic. Este proceso se describe en la próxima sección.

Cómo definir una proyección

Define una constante que contenga las columnas que quieras mostrar como resultado de tu búsqueda. Cada elemento de ListView muestra el nombre visible del contacto, que contiene la forma principal del nombre del contacto. En Android 3.0 (versión de API 11) y versiones posteriores, el nombre de esta columna es Contacts.DISPLAY_NAME_PRIMARY; en versiones anteriores, su nombre es Contacts.DISPLAY_NAME.

El proceso de vinculación de SimpleCursorAdapter usa la columna Contacts._ID. Contacts._ID y LOOKUP_KEY se usan juntos para crear un URI de contenido para el contacto que selecciona el usuario.

Kotlin

...
@SuppressLint("InlinedApi")
private val PROJECTION: Array<out String> = arrayOf(
        ContactsContract.Contacts._ID,
        ContactsContract.Contacts.LOOKUP_KEY,
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
            ContactsContract.Contacts.DISPLAY_NAME_PRIMARY
        else
            ContactsContract.Contacts.DISPLAY_NAME
)

Java

...
@SuppressLint("InlinedApi")
private static final String[] PROJECTION =
        {
            Contacts._ID,
            Contacts.LOOKUP_KEY,
            Build.VERSION.SDK_INT
                    >= Build.VERSION_CODES.HONEYCOMB ?
                    ContactsContract.Contacts.DISPLAY_NAME_PRIMARY :
                    ContactsContract.Contacts.DISPLAY_NAME

        };

Cómo definir constantes para los índices de las columnas del Cursor

Para obtener datos de una columna individual en un Cursor, necesitas el índice de la columna dentro del Cursor. Puedes definir constantes para los índices de las columnas Cursor, ya que los índices son los mismos que el orden de los nombres de las columnas en tu proyección. Por ejemplo:

Kotlin

// The column index for the _ID column
private const val CONTACT_ID_INDEX: Int = 0
// The column index for the CONTACT_KEY column
private const val CONTACT_KEY_INDEX: Int = 1

Java

// The column index for the _ID column
private static final int CONTACT_ID_INDEX = 0;
// The column index for the CONTACT_KEY column
private static final int CONTACT_KEY_INDEX = 1;

Cómo especificar los criterios de selección

Para especificar los datos que deseas, crea una combinación de expresiones de texto y variables que le indiquen al proveedor las columnas de datos que debe buscar y los valores que debe encontrar.

Para la expresión de texto, define una constante que enumere las columnas de búsqueda. Aunque esta expresión también puede contener valores, la práctica preferida es representarlos con un marcador de posición "?". Durante la recuperación, el marcador de posición se reemplaza por valores de un array. Usar "?" como marcador de posición garantiza que la especificación de búsqueda se genere a través de la vinculación en lugar de la compilación de SQL. Esta práctica elimina la posibilidad de inyección de SQL malicioso. Por ejemplo:

Kotlin

// Defines the text expression
@SuppressLint("InlinedApi")
private val SELECTION: String =
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
            "${ContactsContract.Contacts.DISPLAY_NAME_PRIMARY} LIKE ?"
        else
            "${ContactsContract.Contacts.DISPLAY_NAME} LIKE ?"
...
    // Defines a variable for the search string
    private val searchString: String = ...
    // Defines the array to hold values that replace the ?
    private val selectionArgs = arrayOf<String>(searchString)

Java

    // 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 searchString;
    // Defines the array to hold values that replace the ?
    private String[] selectionArgs = { searchString };

Cómo definir el método onItemClick()

En una sección anterior, estableciste un objeto de escucha para hacer clic en un elemento del elemento ListView. Ahora, implementa la acción para el objeto de escucha definiendo el método AdapterView.OnItemClickListener.onItemClick():

Kotlin

    override fun onItemClick(parent: AdapterView<*>, view: View?, position: Int, id: Long) {
        // Get the Cursor
        val cursor: Cursor? = (parent.adapter as? CursorAdapter)?.cursor?.apply {
            // Move to the selected contact
            moveToPosition(position)
            // Get the _ID value
            contactId = getLong(CONTACT_ID_INDEX)
            // Get the selected LOOKUP KEY
            contactKey = getString(CONTACT_KEY_INDEX)
            // Create the contact's content Uri
            contactUri = ContactsContract.Contacts.getLookupUri(contactId, mContactKey)
            /*
             * You can use contactUri as the content URI for retrieving
             * the details for a contact.
             */
        }
    }

Java

    @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
        contactId = cursor.getLong(CONTACT_ID_INDEX);
        // Get the selected LOOKUP KEY
        contactKey = cursor.getString(CONTACT_KEY_INDEX);
        // Create the contact's content Uri
        contactUri = Contacts.getLookupUri(contactId, mContactKey);
        /*
         * You can use contactUri as the content URI for retrieving
         * the details for a contact.
         */
    }

Cómo inicializar el cargador

Como usas un CursorLoader para recuperar datos, debes inicializar el subproceso en segundo plano y otras variables que controlan la recuperación asíncrona. Realiza la inicialización en onCreate(), como se muestra en el siguiente ejemplo:

Kotlin

class ContactsFragment :
        Fragment(),
        LoaderManager.LoaderCallbacks<Cursor> {
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        // Always call the super method first
        super.onCreate(savedInstanceState)
        ...
        // Initializes the loader
        loaderManager.initLoader(0, null, this)

Java

public class ContactsFragment extends Fragment implements
        LoaderManager.LoaderCallbacks<Cursor> {
    ...
    // Called just before the Fragment displays its UI
    @Override
    public void onCreate(Bundle savedInstanceState) {
        // Always call the super method first
        super.onCreate(savedInstanceState);
        ...
        // Initializes the loader
        getLoaderManager().initLoader(0, null, this);

Cómo implementar onCreateLoader()

Implementa el método onCreateLoader(), al que llama el framework del cargador inmediatamente después de que llamas a initLoader().

En onCreateLoader(), configura el patrón de cadena de búsqueda. Para convertir una cadena en un patrón, inserta caracteres "%" (porcentaje) para representar una secuencia de cero o más caracteres, o caracteres "_" (guion bajo) para representar un solo carácter, o ambos. Por ejemplo, el patrón "%Jefferson%" coincidiría con "Thomas Jefferson" y "Jefferson Davis".

Muestra un CursorLoader nuevo desde el método. Para el URI de contenido, usa Contacts.CONTENT_URI. Este URI hace referencia a toda la tabla, como se muestra en el siguiente ejemplo:

Kotlin

    ...
    override fun onCreateLoader(loaderId: Int, args: Bundle?): Loader<Cursor> {
        /*
         * Makes search string into pattern and
         * stores it in the selection array
         */
        selectionArgs[0] = "%$mSearchString%"
        // Starts the query
        return activity?.let {
            return CursorLoader(
                    it,
                    ContactsContract.Contacts.CONTENT_URI,
                    PROJECTION,
                    SELECTION,
                    selectionArgs,
                    null
            )
        } ?: throw IllegalStateException()
    }

Java

    ...
    @Override
    public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
        /*
         * Makes search string into pattern and
         * stores it in the selection array
         */
        selectionArgs[0] = "%" + searchString + "%";
        // Starts the query
        return new CursorLoader(
                getActivity(),
                ContactsContract.Contacts.CONTENT_URI,
                PROJECTION,
                SELECTION,
                selectionArgs,
                null
        );
    }

Cómo implementar onLoadFinished() y onLoaderReset()

Implementa el método onLoadFinished(). El framework del cargador llama a onLoadFinished() cuando el proveedor de contactos devuelve los resultados de la consulta. En este método, coloca el resultado Cursor en SimpleCursorAdapter. Esto actualiza automáticamente el ListView con los resultados de la búsqueda:

Kotlin

    override fun onLoadFinished(loader: Loader<Cursor>, cursor: Cursor) {
        // Put the result Cursor in the adapter for the ListView
        cursorAdapter?.swapCursor(cursor)
    }

Java

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        // Put the result Cursor in the adapter for the ListView
        cursorAdapter.swapCursor(cursor);
    }

El método onLoaderReset() se invoca cuando el framework del cargador detecta que el resultado Cursor contiene datos obsoletos. Borra la referencia SimpleCursorAdapter al Cursor existente. Si no lo haces, el framework del cargador no reciclará el Cursor, lo que provocará una fuga de memoria. Por ejemplo:

Kotlin

    override fun onLoaderReset(loader: Loader<Cursor>) {
        // Delete the reference to the existing Cursor
        cursorAdapter?.swapCursor(null)
    }

Java

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        // Delete the reference to the existing Cursor
        cursorAdapter.swapCursor(null);

    }

Ahora tienes las partes clave de una app que hace coincidir una cadena de búsqueda con nombres de contactos y devuelve el resultado en un ListView. El usuario puede hacer clic en un nombre de contacto para seleccionarlo. De esta manera, se activa un objeto de escucha, en el que puedes realizar acciones adicionales con los datos del contacto, Por ejemplo, puedes recuperar los detalles del contacto. Para aprender a hacerlo, continúa con la siguiente lección, Cómo recuperar los detalles de un contacto.

Para obtener más información sobre las interfaces de usuario de búsqueda, consulta la guía de la API Cómo crear una interfaz de búsqueda.

En las secciones restantes de esta lección, se muestran otras formas de encontrar contactos en el proveedor de contactos.

Cómo hacer coincidir un contacto por un tipo específico de datos

Esta técnica te permite especificar el tipo de datos que deseas que coincidan. La recuperación por nombre es un ejemplo específico de este tipo de consulta, pero también puedes hacerlo para cualquiera de los tipos de datos de detalles asociados con un contacto. Por ejemplo, puedes recuperar contactos que tengan un código postal específico; en este caso, la cadena de búsqueda debe coincidir con los datos almacenados en una fila de código postal.

Para implementar este tipo de recuperación, primero implementa el siguiente código, como se indica en las secciones anteriores:

  • Cómo solicitar permiso para leer datos del proveedor
  • Cómo definir diseños de elementos y ListView
  • Cómo definir un fragmento que muestre la lista de contactos
  • Cómo definir variables globales
  • Cómo inicializar el fragmento
  • Cómo configurar el CursorAdapter para la ListView
  • Cómo configurar el objeto de escucha del contacto seleccionado
  • Cómo definir constantes para los índices de columnas del Cursor

    Aunque recuperas datos de una tabla diferente, el orden de las columnas en la proyección es el mismo, por lo que puedes usar los mismos índices para el Cursor.

  • Cómo definir el método onItemClick()
  • Cómo inicializar el cargador
  • Cómo implementar onLoadFinished() y onLoaderReset()

En los siguientes pasos, se muestra el código adicional que necesitas para hacer coincidir una cadena de búsqueda con un tipo particular de datos de detalles y mostrar los resultados.

Cómo seleccionar el tipo de datos y la tabla

Para buscar un tipo particular de datos de detalles, debes conocer el valor del tipo de MIME personalizado para el tipo de datos. Cada tipo de datos tiene un valor de tipo de MIME único definido por una constante CONTENT_ITEM_TYPE en la subclase de ContactsContract.CommonDataKinds asociada con el tipo de datos. Las subclases tienen nombres que indican su tipo de datos; por ejemplo, la subclase para los datos de correo electrónico es ContactsContract.CommonDataKinds.Email, y el tipo de MIME personalizado para los datos de correo electrónico se define con la constante Email.CONTENT_ITEM_TYPE.

Usa la tabla ContactsContract.Data para tu búsqueda. Todas las constantes que necesitas para tu proyección, cláusula de selección y orden de clasificación se definen en esta tabla o se heredan de ella.

Cómo definir una proyección

Para definir una proyección, elige una o más de las columnas definidas en ContactsContract.Data o las clases de las que hereda. El proveedor de contactos realiza una unión implícita entre ContactsContract.Data y otras tablas antes de devolver filas. Por ejemplo:

Kotlin

@SuppressLint("InlinedApi")
private val PROJECTION: Array<out String> = arrayOf(
        /*
         * The detail data row ID. To make a ListView work,
         * this column is required.
         */
        ContactsContract.Data._ID,
        // The primary display name
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
            ContactsContract.Data.DISPLAY_NAME_PRIMARY
        else
            ContactsContract.Data.DISPLAY_NAME,
        // The contact's _ID, to construct a content URI
        ContactsContract.Data.CONTACT_ID,
        // The contact's LOOKUP_KEY, to construct a content URI
        ContactsContract.Data.LOOKUP_KEY
)

Java

    @SuppressLint("InlinedApi")
    private static final String[] PROJECTION =
        {
            /*
             * The detail data row ID. To make a ListView work,
             * this column is required.
             */
            ContactsContract.Data._ID,
            // The primary display name
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ?
                    ContactsContract.Data.DISPLAY_NAME_PRIMARY :
                    ContactsContract.Data.DISPLAY_NAME,
            // The contact's _ID, to construct a content URI
            ContactsContract.Data.CONTACT_ID,
            // The contact's LOOKUP_KEY, to construct a content URI
            ContactsContract.Data.LOOKUP_KEY // A permanent link to the contact
        };

Cómo definir criterios de búsqueda

Para buscar una cadena dentro de un tipo de datos en particular, crea una cláusula de selección a partir de lo siguiente:

  • El nombre de la columna que contiene tu string de búsqueda: Este nombre varía según el tipo de datos, por lo que debes encontrar la subclase de ContactsContract.CommonDataKinds que corresponde al tipo de datos y, luego, elegir el nombre de la columna de esa subclase. Por ejemplo, para buscar direcciones de correo electrónico, usa la columna Email.ADDRESS.
  • La cadena de búsqueda en sí, representada como el carácter "?" en la cláusula de selección.
  • El nombre de la columna que contiene el valor de tipo MIME personalizado: Este nombre siempre es Data.MIMETYPE.
  • El valor de tipo de MIME personalizado del tipo de datos: Como se describió anteriormente, esta es la constante CONTENT_ITEM_TYPE en la subclase ContactsContract.CommonDataKinds. Por ejemplo, el valor del tipo MIME para los datos de correo electrónico es Email.CONTENT_ITEM_TYPE. Encierra el valor entre comillas simples concatenando un carácter "'" (comilla simple) al principio y al final de la constante. De lo contrario, el proveedor interpretará el valor como un nombre de variable en lugar de como un valor de cadena. No necesitas usar un marcador de posición para este valor, ya que estás usando una constante en lugar de un valor proporcionado por el usuario.

Por ejemplo:

Kotlin

/*
 * Constructs search criteria from the search string
 * and email MIME type
 */
private val SELECTION: String =
        /*
         * 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.
         */
        "${ContactsContract.Data.MIMETYPE } = '${Email.CONTENT_ITEM_TYPE}'"

Java

    /*
     * 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.
             */
            ContactsContract.Data.MIMETYPE + " = '" + Email.CONTENT_ITEM_TYPE + "'";

A continuación, define variables para contener el argumento de selección:

Kotlin

    private var searchString: String? = null
    private val selectionArgs: Array<String> = arrayOf("")

Java

    String searchString;
    String[] selectionArgs = { "" };

Cómo implementar onCreateLoader()

Ahora que especificaste los datos que deseas y cómo encontrarlos, define una consulta en tu implementación de onCreateLoader(). Muestra un CursorLoader nuevo desde este método con tu proyección, la expresión de texto de selección y el array de selección como argumentos. Para un URI de contenido, usa Data.CONTENT_URI. Por ejemplo:

Kotlin

    override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
        // OPTIONAL: Makes search string into pattern
        searchString = "%$mSearchString%"

        searchString?.also {
            // Puts the search string into the selection criteria
            selectionArgs[0] = it
        }
        // Starts the query
        return activity?.let {
            CursorLoader(
                    it,
                    ContactsContract.Data.CONTENT_URI,
                    PROJECTION,
                    SELECTION,
                    selectionArgs,
                    null
            )
        } ?: throw IllegalStateException()
    }

Java

@Override
    public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
        // OPTIONAL: Makes search string into pattern
        searchString = "%" + searchString + "%";
        // Puts the search string into the selection criteria
        selectionArgs[0] = searchString;
        // Starts the query
        return new CursorLoader(
                getActivity(),
                Data.CONTENT_URI,
                PROJECTION,
                SELECTION,
                selectionArgs,
                null
        );
    }

Estos fragmentos de código son la base de una búsqueda inversa básica basada en un tipo específico de datos de detalle. Esta es la mejor técnica para usar si tu app se enfoca en un tipo de datos en particular, como correos electrónicos, y quieres permitir que los usuarios obtengan los nombres asociados con un fragmento de datos.

Cómo hacer coincidir un contacto usando cualquier tipo de datos

Si recuperas un contacto en función de cualquier tipo de datos, se mostrarán los contactos si alguno de sus datos coincide con la cadena de búsqueda, incluidos el nombre, la dirección de correo electrónico, la dirección postal, el número de teléfono, etcétera. De esta manera, se obtiene un amplio conjunto de resultados de la búsqueda. Por ejemplo, si la cadena de búsqueda es "Pérez", la búsqueda de cualquier tipo de datos muestra el contacto "Juan Pérez" y también los contactos que viven en la "calle Pérez".

Para implementar este tipo de recuperación, primero implementa el siguiente código, como se indica en las secciones anteriores:

  • Cómo solicitar permiso para leer datos del proveedor
  • Cómo definir diseños de elementos y ListView
  • Cómo definir un fragmento que muestre la lista de contactos
  • Cómo definir variables globales
  • Cómo inicializar el fragmento
  • Cómo configurar el CursorAdapter para la ListView
  • Cómo configurar el objeto de escucha del contacto seleccionado
  • Cómo definir una proyección
  • Cómo definir constantes para los índices de columnas del Cursor

    Para este tipo de recuperación, usas la misma tabla que usaste en la sección Haz coincidir un contacto por nombre y enumera los resultados. También debes usar los mismos índices de columna.

  • Cómo definir el método onItemClick()
  • Cómo inicializar el cargador
  • Cómo implementar onLoadFinished() y onLoaderReset()

En los siguientes pasos, se muestra el código adicional que necesitas para hacer coincidir una cadena de búsqueda con cualquier tipo de datos y mostrar los resultados.

Cómo quitar criterios de selección

No definas las constantes SELECTION ni la variable mSelectionArgs, ya que no se usan en este tipo de obtención.

Cómo implementar onCreateLoader()

Implementa el método onCreateLoader() y muestra un nuevo CursorLoader. No es necesario que conviertas la cadena de búsqueda en un patrón, ya que el proveedor de contactos lo hace automáticamente. Usa Contacts.CONTENT_FILTER_URI como el URI base y agrega tu cadena de búsqueda llamando a Uri.withAppendedPath(). Usar este URI activa automáticamente la búsqueda de cualquier tipo de datos, como se muestra en el siguiente ejemplo:

Kotlin

    override fun onCreateLoader(loaderId: Int, args: Bundle?): Loader<Cursor> {
        /*
         * Appends the search string to the base URI. Always
         * encode search strings to ensure they're in proper
         * format.
         */
        val contentUri: Uri = Uri.withAppendedPath(
                ContactsContract.Contacts.CONTENT_FILTER_URI,
                Uri.encode(searchString)
        )
        // Starts the query
        return activity?.let {
            CursorLoader(
                    it,
                    contentUri,
                    PROJECTION2,
                    null,
                    null,
                    null
            )
        } ?: throw IllegalStateException()
    }

Java

    @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(searchString));
        // Starts the query
        return new CursorLoader(
                getActivity(),
                contentUri,
                PROJECTION,
                null,
                null,
                null
        );
    }

Estos fragmentos de código son la base de una app que realiza una búsqueda más amplia en el Proveedor de contactos. Esta técnica es útil para las apps que quieren implementar una funcionalidad similar a la pantalla de la lista de contactos de la app de Contactos.