Cómo obtener detalles de un contacto

En esta lección, se muestra cómo obtener datos detallados de un contacto, como direcciones de correo electrónico y números de teléfono, entre otros. Estos son los detalles que buscan los usuarios cuando acceden a un contacto. Puedes proporcionarles todos los detalles de un contacto o solo un tipo específico, como las direcciones de correo electrónico.

En los pasos que se describen en esta lección, se asume que ya tienes una fila de ContactsContract.Contacts para un contacto que el usuario seleccionó. En la lección Cómo obtener nombres de contactos, se muestra cómo obtener una lista de contactos.

Obtén todos los detalles de un contacto

Si quieres obtener todos los detalles de un contacto, busca en la tabla ContactsContract.Data cualquier fila que contenga la LOOKUP_KEY del contacto. Esta columna está disponible en la tabla ContactsContract.Data, porque el Proveedor de contactos hace una unión implícita entre las tablas ContactsContract.Contacts y ContactsContract.Data. En la lección Cómo obtener nombres de contactos, se describe la columna LOOKUP_KEY en mayor detalle.

Nota: Obtener todos los detalles de un contacto reduce el rendimiento de un dispositivo, ya que este debe obtener todas las columnas de la tabla ContactsContract.Data. Antes de usar esta técnica, considera el impacto que tendrá en el rendimiento.

Solicita los permisos

Si quieres leer datos del Proveedor de contactos, debes tener el permiso READ_CONTACTS. Para solicitarlo, agrega el siguiente elemento secundario de <manifest> en tu archivo de manifiesto:

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

Configura una proyección

En función del tipo de datos que contiene una fila, se usan pocas o varias columnas. Además, los datos se ubican en columnas diferentes según su tipo. A fin de asegurarte de obtener todas las columnas posibles para todos los tipos de datos disponibles, debes agregar todos los nombres de columnas en tu proyección. Siempre debes obtener el Data._ID si quieres vincular el Cursor resultante a una ListView; de lo contrario, la vinculación no funcionará. También debes obtener Data.MIMETYPE para poder identificar el tipo de datos de cada fila que obtengas. Por ejemplo:

Kotlin

    private val PROJECTION: Array<out String> = arrayOf(
            ContactsContract.Data._ID,
            ContactsContract.Data.MIMETYPE,
            ContactsContract.Data.DATA1,
            ContactsContract.Data.DATA2,
            ContactsContract.Data.DATA3,
            ContactsContract.Data.DATA4,
            ContactsContract.Data.DATA5,
            ContactsContract.Data.DATA6,
            ContactsContract.Data.DATA7,
            ContactsContract.Data.DATA8,
            ContactsContract.Data.DATA9,
            ContactsContract.Data.DATA10,
            ContactsContract.Data.DATA11,
            ContactsContract.Data.DATA12,
            ContactsContract.Data.DATA13,
            ContactsContract.Data.DATA14,
            ContactsContract.Data.DATA15
    )
    

Java

        private static final String[] PROJECTION =
                {
                    ContactsContract.Data._ID,
                    ContactsContract.Data.MIMETYPE,
                    ContactsContract.Data.DATA1,
                    ContactsContract.Data.DATA2,
                    ContactsContract.Data.DATA3,
                    ContactsContract.Data.DATA4,
                    ContactsContract.Data.DATA5,
                    ContactsContract.Data.DATA6,
                    ContactsContract.Data.DATA7,
                    ContactsContract.Data.DATA8,
                    ContactsContract.Data.DATA9,
                    ContactsContract.Data.DATA10,
                    ContactsContract.Data.DATA11,
                    ContactsContract.Data.DATA12,
                    ContactsContract.Data.DATA13,
                    ContactsContract.Data.DATA14,
                    ContactsContract.Data.DATA15
                };
    

En esta proyección, se obtienen todas las columnas para una fila en la tabla ContactsContract.Data, que usa los nombres de columna definidos en la clase ContactsContract.Data.

De manera opcional, también puedes usar otras constantes de columnas que se definieron en la clase ContactsContract.Data o que esta heredó. Sin embargo, ten en cuenta que las columnas SYNC1 a SYNC4 son para los adaptadores de sincronización, por los que sus datos no son útiles.

Define los criterios de selección

Define una constante en tu cláusula de selección, un arreglo para conservar argumentos de selección y una variable a fin de mantener el valor de selección. Usa la columna Contacts.LOOKUP_KEY para buscar el contacto. Por ejemplo:

Kotlin

    // Defines the selection clause
    private const val SELECTION: String = "${ContactsContract.Data.LOOKUP_KEY} = ?"
    ...
    // Defines the array to hold the search criteria
    private val selectionArgs: Array<String> = arrayOf("")
    /*
     * Defines a variable to contain the selection value. Once you
     * have the Cursor from the Contacts table, and you've selected
     * the desired row, move the row's LOOKUP_KEY value into this
     * variable.
     */
    private var lookupKey: String? = null
    

Java

        // Defines the selection clause
        private static final String SELECTION = Data.LOOKUP_KEY + " = ?";
        // Defines the array to hold the search criteria
        private String[] selectionArgs = { "" };
        /*
         * Defines a variable to contain the selection value. Once you
         * have the Cursor from the Contacts table, and you've selected
         * the desired row, move the row's LOOKUP_KEY value into this
         * variable.
         */
        private lateinit var lookupKey: String
    

Usar "?" como marcador de posición en tu expresión de texto de selección garantiza que la búsqueda resultante se genere por medio de una vinculación en lugar de una compilación de SQL. Este enfoque elimina la posibilidad de una inyección de SQL malicioso.

Define el orden de clasificación

Define el orden de clasificación que quieras para el Cursor resultante. A fin de mantener juntas todas las filas de un tipo específico de datos, ordénalas por Data.MIMETYPE. Este argumento de búsqueda agrupa todas las filas de correo electrónico, todas las filas de teléfonos y así sucesivamente. Por ejemplo:

Kotlin

    /*
     * Defines a string that specifies a sort order of MIME type
     */
    private const val SORT_ORDER = ContactsContract.Data.MIMETYPE
    

Java

        /*
         * Defines a string that specifies a sort order of MIME type
         */
        private static final String SORT_ORDER = ContactsContract.Data.MIMETYPE;
    

Nota: Algunos tipos de datos no usan un subtipo, por lo que no podrás ordenarlos por esta categoría. En su lugar, debes iterarlos a través del Cursor resultante, determinar el tipo de datos de la fila actual y almacenar los datos para las filas que usan un subtipo. Una vez que termines de leer el cursor, podrás ordenar cada tipo por subtipo y mostrar los resultados.

Inicializa el cargador

Siempre debes realizar las obtenciones del Proveedor de Contactos (y todos los otros proveedores de contenido) en un subproceso en segundo plano. Usa el marco de trabajo del cargador que define la clase LoaderManager y la interfaz LoaderManager.LoaderCallbacks para obtenerlos en segundo plano.

Cuando estés listo para obtener las filas, inicializa el marco de trabajo del cargador mediante una llamada a initLoader(). Pasa un identificador de número entero al método; este identificador se pasa a los métodos LoaderManager.LoaderCallbacks. El identificador te ayuda a usar varios cargadores en una app ya que te permite diferenciarlos.

En el siguiente fragmento, se muestra cómo inicializar el marco de trabajo del cargador:

Kotlin

    // Defines a constant that identifies the loader
    private const val DETAILS_QUERY_ID: Int = 0

    class DetailsFragment : Fragment(), LoaderManager.LoaderCallbacks<Cursor> {
        ...
        override fun onActivityCreated(savedInstanceState: Bundle?) {
            ...
            // Initializes the loader framework
            loaderManager.initLoader(DETAILS_QUERY_ID, null, this)
    

Java

    public class DetailsFragment extends Fragment implements
            LoaderManager.LoaderCallbacks<Cursor> {
        ...
        // Defines a constant that identifies the loader
        static int DETAILS_QUERY_ID = 0;
        ...
        /*
         * Invoked when the parent Activity is instantiated
         * and the Fragment's UI is ready. Put final initialization
         * steps here.
         */
        @Override
        onActivityCreated(Bundle savedInstanceState) {
            ...
            // Initializes the loader framework
            getLoaderManager().initLoader(DETAILS_QUERY_ID, null, this);
    

Implementa onCreateLoader()

Implementa el método onCreateLoader(), al que el marco del trabajo del cargador llamará inmediatamente después de que invoques el initLoader(). Muestra un CursorLoader desde este método. Como estás buscando en la tabla ContactsContract.Data, usa la constante Data.CONTENT_URI como URI de contenido. Por ejemplo:

Kotlin

    override fun onCreateLoader(loaderId: Int, args: Bundle?): Loader<Cursor> {
        // Choose the proper action
        mLoader = when(loaderId) {
            DETAILS_QUERY_ID -> {
                // Assigns the selection parameter
                selectionArgs[0] = lookupKey
                // Starts the query
                activity?.let {
                    CursorLoader(
                            it,
                            ContactsContract.Data.CONTENT_URI,
                            PROJECTION,
                            SELECTION,
                            selectionArgs,
                            SORT_ORDER
                    )
                }
            }
            ...
        }
        return mLoader
    }
    

Java

    @Override
        public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
            // Choose the proper action
            switch (loaderId) {
                case DETAILS_QUERY_ID:
                // Assigns the selection parameter
                selectionArgs[0] = lookupKey;
                // Starts the query
                CursorLoader mLoader =
                        new CursorLoader(
                                getActivity(),
                                ContactsContract.Data.CONTENT_URI,
                                PROJECTION,
                                SELECTION,
                                selectionArgs,
                                SORT_ORDER
                        );
        }
    

Implementa 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. Por ejemplo:

Kotlin

        override fun onLoadFinished(loader: Loader<Cursor>, data: Cursor) {
            when(loader.id) {
                DETAILS_QUERY_ID -> {
                    /*
                     * Process the resulting Cursor here.
                     */
                }
                ...
            }
        }
    

Java

        @Override
        public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
            switch (loader.getId()) {
                case DETAILS_QUERY_ID:
                        /*
                         * Process the resulting Cursor here.
                         */
                    }
                    break;
                ...
            }
        }
    

Se invoca el método onLoaderReset() cuando el marco de trabajo del cargador detecta que se modificaron los datos en los que se basa el Cursor resultante. En este punto, configura su valor en nulo para quitar cualquier referencia al Cursor existente. Si no lo haces, el marco de trabajo del cargador no destruirá el Cursor antiguo, lo que provocará una pérdida de memoria. Por ejemplo:

Kotlin

        override fun onLoaderReset(loader: Loader<Cursor>) {
            when (loader.id) {
                DETAILS_QUERY_ID -> {
                    /*
                     * If you have current references to the Cursor,
                     * remove them here.
                     */
                }
                ...
            }
        }
    

Java

        @Override
        public void onLoaderReset(Loader<Cursor> loader) {
            switch (loader.getId()) {
                case DETAILS_QUERY_ID:
                    /*
                     * If you have current references to the Cursor,
                     * remove them here.
                     */
                    }
                    break;
        }
    

Obtén detalles específicos de un contacto

La obtención de un tipo específico de datos de un contacto, como todos sus correos electrónicos, tiene el mismo patrón que la obtención de todos los detalles. Estos son los únicos cambios que necesitas realizar en el código y se enumeran en Cómo obtener todos los detalles de un contacto:

Proyección
Modifica tu proyección para obtener las columnas que son específicas del tipo de datos. Además, modifica la proyección a fin de usar las constantes de nombres de columna definidas en la subclase ContactsContract.CommonDataKinds que corresponde al tipo de datos.
Selección
Modifica el texto de selección para buscar el valor MIMETYPE que es específico del tipo de datos.
Orden
Como solo seleccionas un único tipo de datos, no debes agrupar el Cursor resultante por Data.MIMETYPE.

Estas modificaciones se describen en las siguientes secciones.

Define una proyección

Define las columnas que quieras obtener con las constantes de nombres de columna en la subclase de ContactsContract.CommonDataKinds del tipo de datos. Si deseas vincular tu Cursor a una ListView, asegúrate de obtener el _ID de la columna. Por ejemplo, para obtener los datos de correo electrónico, define la siguiente proyección:

Kotlin

    private val PROJECTION: Array<String> = arrayOf(
            ContactsContract.CommonDataKinds.Email._ID,
            ContactsContract.CommonDataKinds.Email.ADDRESS,
            ContactsContract.CommonDataKinds.Email.TYPE,
            ContactsContract.CommonDataKinds.Email.LABEL
    )
    

Java

        private static final String[] PROJECTION =
                {
                    ContactsContract.CommonDataKinds.Email._ID,
                    ContactsContract.CommonDataKinds.Email.ADDRESS,
                    ContactsContract.CommonDataKinds.Email.TYPE,
                    ContactsContract.CommonDataKinds.Email.LABEL
                };
    

Ten en cuenta que esta proyección usa los nombres de columna definidos en la clase ContactsContract.CommonDataKinds.Email, en lugar de los que se definieron en la clase ContactsContract.Data. Usar nombres de columna específicos de correos electrónicos hace que el código sea más legible.

En la proyección, también puedes usar otras columnas definidas en la subclase ContactsContract.CommonDataKinds.

Define criterios de selección

Define una expresión de texto de búsqueda que obtenga las filas LOOKUP_KEY de un contacto específico y el Data.MIMETYPE de los detalles que deseas. Encierra el valor de MIMETYPE 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á la constante 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

    /*
     * Defines the selection clause. Search for a lookup key
     * and the Email MIME type
     */
    private const val SELECTION =
            "${ContactsContract.Data.LOOKUP_KEY} = ? AND " +
            "${ContactsContract.Data.MIMETYPE} = '${Email.CONTENT_ITEM_TYPE}'"
    ...
    // Defines the array to hold the search criteria
    private val selectionArgs: Array<String> = arrayOf("")
    

Java

        /*
         * Defines the selection clause. Search for a lookup key
         * and the Email MIME type
         */
        private static final String SELECTION =
                Data.LOOKUP_KEY + " = ?" +
                " AND " +
                Data.MIMETYPE + " = " +
                "'" + Email.CONTENT_ITEM_TYPE + "'";
        // Defines the array to hold the search criteria
        private String[] selectionArgs = { "" };
    

Define un orden de clasificación

Define un orden de clasificación para el Cursor resultante. Como lo que obtienes es un tipo de datos específico, deberás omitir el orden en MIMETYPE. En su lugar, si el tipo de datos detallados que buscas incluye un subtipo, ordénalos por subtipo. Por ejemplo, en el caso de los datos de correo electrónico, puedes ordenarlos por Email.TYPE:

Kotlin

    private const val SORT_ORDER: String = "${Email.TYPE} ASC"
    

Java

        private static final String SORT_ORDER = Email.TYPE + " ASC ";