Cómo obtener una lista de contactos

En esta lección, se muestra cómo obtener una lista de contactos cuyos datos coinciden con toda la string de búsqueda o parte de ella, por medio de las siguientes técnicas:

Hacer coincidir los nombres de contactos
Haz coincidir la string de búsqueda con todos los datos del nombre del contacto o parte de él para obtener una lista de contactos. El Proveedor de contactos permite varias instancias del mismo nombre, por lo que esta técnica puede mostrar una lista de coincidencias.
Hacer coincidir un tipo específico de datos, como un número de teléfono
Haz coincidir la string de búsqueda con un tipo de datos detallados en particular, como una dirección de correo electrónico, para obtener una lista de contactos. Por ejemplo, esta técnica te permite enumerar todos los contactos cuya dirección de correo electrónico coincida con la string de búsqueda.
Hacer coincidir cualquier tipo de datos
Haz coincidir la string de búsqueda con cualquier tipo de datos detallados, incluido el nombre, el número de teléfono, la dirección, la dirección de correo electrónico, entre otros, para obtener una lista de contactos. Por ejemplo, esta técnica te permite aceptar cualquier tipo de datos en una string de búsqueda y, luego, obtener una lista de los contactos cuyos datos coincidan con esta.

Nota: En todos los ejemplos de esta lección, se usa un CursorLoader a fin de obtener los datos del Proveedor de contactos. El CursorLoader ejecuta la búsqueda en un subproceso distinto del subproceso de IU. De esta manera, se garantiza que la búsqueda no ralentice los tiempos de respuesta de la IU, lo que implica una mala experiencia del usuario. Para obtener más información, consulta la clase de capacitación Cómo cargar datos en segundo plano.

Cómo solicitar permiso para leer datos del proveedor

A fin de 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 string de búsqueda con el nombre de uno o varios contactos en la tabla ContactsContract.Contacts del Proveedor de contactos. Por lo general, quieres mostrar los resultados en una ListView para permitir que el usuario elija una de las coincidencias.

Cómo definir diseños de elementos y ListView

Para mostrar los resultados de la búsqueda en una ListView, necesitas un archivo de diseño principal que defina toda la IU, incluida la ListView, y un archivo de diseño de elementos que defina una línea de la ListView. Por ejemplo, puedes crear un archivo de diseño principal res/layout/contacts_list_view.xml con el siguiente 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 de ListView incorporado de Android: android:id/list.

Define el archivo de diseño de elementos 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 de TextView incorporado de Android: android:text1.

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

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

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

Para mostrar la lista de contactos, comienza por definir un Fragment que se cargue mediante una Activity. El uso de 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 del contacto que el usuario selecciona en la lista. Con este enfoque, puedes combinar una de las técnicas que se presentan en esta lección con una de las que se describen en la lección Cómo obtener detalles de un contacto.

Para obtener información sobre cómo usar uno o varios objetos Fragment de una Activity, lee la clase de capacitación Cómo crear una IU dinámica con fragmentos.

A fin de ayudarte a escribir búsquedas para el Proveedor de contactos, el marco de trabajo de Android proporciona una clase de contratos llamada ContactsContract, que define constantes y métodos útiles de acceso al proveedor. Cuando usas esta clase, no es necesario que definas tus propias constantes para los URI de contenido, las columnas ni los nombres de tablas. Si quieres usar esta clase, incluye la siguiente declaración:

Kotlin

    import android.provider.ContactsContract
    

Java

    import android.provider.ContactsContract;
    

Como el código usa un CursorLoader para obtener los datos del proveedor, debes especificar que este 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 de 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: Como Contacts.DISPLAY_NAME_PRIMARY requiere Android 3.0 (API nivel 11) o una versión posterior, configurar la minSdkVersion de tu app en 10 o una versión inferior 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 Fragment, agrega el constructor vacío y público que requiere el sistema de 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 la ListView

Configura el SimpleCursorAdapter que vincula los resultados de la búsqueda de la ListView. Para obtener el objeto ListView que muestra los contactos, tienes que llamar a Activity.findViewById() por medio de la actividad principal del 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, por lo general, quieres que el usuario pueda seleccionar un único contacto para continuar con el procesamiento. Por ejemplo, cuando el usuario hace clic en un contacto, puedes mostrar su dirección en un mapa. Si quieres proporcionar esta función, primero debes definir el Fragment actual como el objeto de escucha de clics especificando que la clase implementa AdapterView.OnItemClickListener, como se muestra en la sección Cómo definir un fragmento que muestra una lista de contactos.

Para seguir configurando el objeto de escucha, vincúlalo a la ListView mediante una llamada 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 la ListView, ahora deberás implementar el método onItemClick() requerido, que administra el evento de clics. 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 la ListView muestra el nombre visible del contacto, que contiene el nombre completo de este. En Android 3.0 (API nivel 11) y versiones posteriores, el nombre de esta columna es Contacts.DISPLAY_NAME_PRIMARY; en las versiones anteriores, su nombre es Contacts.DISPLAY_NAME.

La columna Contacts._ID se usa en el proceso de vinculación SimpleCursorAdapter. Se usan las columnas Contacts._ID y LOOKUP_KEY juntas para construir el URI de contenido del 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 del Cursor, ya que estos índices tienen el mismo orden que 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 variables y expresiones de texto que indiquen al proveedor en qué columnas de datos debe buscar y qué valores debe obtener.

Para la expresión de texto, define una constante que enumere las columnas de búsqueda. Si bien esta expresión también puede contener valores, la práctica preferida es representar los valores con un marcador de posición "?". Durante la obtención, el marcador de posición se reemplaza por los valores de un arreglo. El uso de "?" garantiza que la especificación de búsqueda se genere por medio de una vinculación en lugar de una compilación de SQL. Esta práctica elimina la posibilidad de una 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 de la ListView. Ahora, debes implementar la acción para el objeto de escucha mediante la definición del 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 obtener datos, debes inicializar el subproceso en segundo plano y otras variables que controlan la obtención asíncrona. Realiza la inicialización en onActivityCreated(), que se invoca inmediatamente antes de que aparezca la IU de Fragment, como se muestra en el siguiente ejemplo:

Kotlin

    class ContactsFragment :
            Fragment(),
            LoaderManager.LoaderCallbacks<Cursor> {
        ...
        // Called just before the Fragment displays its UI
        override fun onActivityCreated(savedInstanceState: Bundle?) {
            // Always call the super method first
            super.onActivityCreated(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 onActivityCreated(Bundle savedInstanceState) {
            // Always call the super method first
            super.onActivityCreated(savedInstanceState);
            ...
            // Initializes the loader
            getLoaderManager().initLoader(0, null, this);
    

Cómo implementar onCreateLoader()

Deberás implementar el método onCreateLoader(), al que el marco del trabajo del cargador llamará inmediatamente después de que invoques el initLoader().

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

Muestra un CursorLoader desde el método. Para obtener 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 marco de trabajo del cargador llama a onLoadFinished() cuando el Proveedor de contactos muestra los resultados de la búsqueda. En este método, agrega el Cursor resultante en el SimpleCursorAdapter. De esta manera, se actualiza automáticamente la 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 marco de trabajo del cargador detecta que el Cursor resultante contiene datos inactivos. Borra la referencia de SimpleCursorAdapter al Cursor existente. Si no lo haces, el marco de trabajo del cargador no reciclará el Cursor, lo que provocará una pérdida 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 principales de una app que hace coincidir una string de búsqueda con nombres de contactos y muestra el resultado en una 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, obtener sus detalles. Para obtener información sobre cómo hacerlo, continúa con la siguiente lección: Cómo obtener detalles de un contacto.

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

En el resto de las secciones de esta lección, se muestran otras maneras de buscar 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 para el que quieres obtener coincidencias. La obtención por nombre es un ejemplo específico de este tipo de búsqueda, pero también puedes realizarla para cualquier tipo de datos detallados asociados con un contacto. Por ejemplo, puedes obtener contactos que tienen un código postal específico; en este caso, la string de búsqueda tiene que coincidir con los datos almacenados en la fila de código postal.

Para implementar este tipo de obtención, primero debes incluir el siguiente código, como se enumera 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

    Si bien obtendrás datos de tablas diferentes, el orden de las columnas en la proyección será el mismo, por lo que podrás 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 string con un tipo específico de datos detallados y mostrar los resultados.

Cómo seleccionar el tipo de datos y la tabla

Para buscar un tipo específico de datos detallados, debes conocer el valor del tipo de MIME personalizado que está asociado al 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 el tipo de datos; por ejemplo, la de los datos de correo electrónico es ContactsContract.CommonDataKinds.Email y el tipo de MIME personalizado de los datos de correo electrónico se define con la constante Email.CONTENT_ITEM_TYPE.

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

Cómo definir una proyección

Para definir una proyección, selecciona una o varias columnas definidas en ContactsContract.Data o las clases de las cuales se hereda. El Proveedor de contactos realiza una unión implícita entre ContactsContract.Data y otras tablas antes de mostrar 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 string dentro de un tipo específico de datos, 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 varía según el tipo de datos, por lo que debes buscar la subclase de ContactsContract.CommonDataKinds que corresponde al tipo de datos y, luego, seleccionar 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, que se representa 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 siempre es Data.MIMETYPE
  • El valor de tipo 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 de tipo MIME de los datos de correo electrónico es Email.CONTENT_ITEM_TYPE. Encierra el valor entre comillas simples mediante la concatenación de un carácter "'" (comilla simple) al principio y al final de la constante; de lo contrario, el proveedor interpretará el valor como nombre de variable en lugar de como valor de string. Como usas una constante en lugar de un valor proporcionado por el usuario, no necesitas usar un marcador de posición para este valor

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 quieres y la forma de encontrarlos, define una búsqueda en tu implementación de onCreateLoader(). Muestra un nuevo CursorLoader mediante este método, usando tu proyección, la expresión de texto de selección y un arreglo de selección como argumentos. Para obtener 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 simple que se basa en un tipo específico de datos detallados. Esta es la mejor técnica que puedes implementar si tu app se enfoca en un tipo específico de datos, como correos electrónicos, y quieres permitir que los usuarios obtengan los nombres asociados con una porción de datos.

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

La obtención de un contacto en función de cualquier tipo de datos muestra contactos cuando cualquiera de los datos coincide con una string de búsqueda, incluido el nombre, la dirección de correo electrónico, la dirección, el número de teléfono, entre otros. De esta manera, se obtiene un amplio conjunto de resultados de la búsqueda. Por ejemplo, si la string de búsqueda es "Doe" y buscas cualquier tipo de datos, se mostrará el contacto "John Doe", pero también los contactos que viven en la "calle Doe".

Para implementar este tipo de obtención, primero debes incluir el siguiente código, como se enumera 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 obtención, usarás la misma tabla de la sección Cómo hacer coincidir un contacto por nombre y enumerar los resultados. Además, 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 string con cualquier tipo de datos y mostrar los resultados.

Cómo quitar criterios de selección

No definas las constantes de 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 necesitas convertir la string 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 de base, y adjunta tu string de búsqueda a este mediante una llamada 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 lista de contactos de la app de Personas.