En esta lección, se muestra cómo recuperar datos detallados de un contacto, como direcciones de correo electrónico, números de teléfono , etcétera. Estos son los detalles que buscan los usuarios cuando acceden a un contacto. Puedes proporcionarles todos los detalles de un contacto o mostrar solo los detalles de un tipo en particular, como las direcciones de correo electrónico.
En los pasos de esta lección, se supone que ya tienes una
ContactsContract.Contacts fila para un contacto que eligió el usuario.
En la lección Cómo recuperar nombres de contactos, se muestra cómo
recuperar una lista de contactos.
Cómo obtener todos los detalles de un contacto
Para recuperar todos los detalles de un contacto, busca en la
ContactsContract.Data tabla las filas que contengan la
LOOKUP_KEY del contacto. Esta columna está disponible en
la tabla ContactsContract.Data, ya que el proveedor de contactos realiza una unión implícita entre la tabla ContactsContract.Contacts
y la tabla ContactsContract.Data. La columna
LOOKUP_KEY se describe
con más detalle en la lección Cómo recuperar nombres de contactos.
Nota: Recuperar todos los detalles de un contacto reduce el rendimiento de un
dispositivo, ya que necesita recuperar todas las columnas de la
ContactsContract.Data tabla. Considera el impacto en el rendimiento antes
de usar esta técnica.
Solicitar permisos
Para leer desde el proveedor de contactos, tu app debe tener
READ_CONTACTS permiso.
Para solicitar este permiso, agrega el siguiente elemento secundario de
<manifest> a 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 encuentran en diferentes columnas según el tipo de datos.
Para asegurarte de obtener todas las columnas posibles para todos los tipos de datos posibles, debes agregar todos los
nombres de las columnas a tu proyección. Siempre recupera
Data._ID si vinculas el resultado
Cursor a un ListView; de lo contrario, la vinculación
no funcionará. También recupera Data.MIMETYPE
para que puedas identificar el tipo de datos de cada fila que recuperes. 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 };
Esta proyección recupera todas las columnas de una fila en la
ContactsContract.Data tabla con los nombres de las columnas definidos en
la ContactsContract.Data clase.
De manera opcional, también puedes usar cualquier otra constante de columna definida en la clase
ContactsContract.Data o heredada por ella. Sin embargo, ten en cuenta que las columnas
SYNC1 a
SYNC4 están diseñadas para que las usen los adaptadores de sincronización,
por lo que sus datos no son útiles.
Cómo definir los criterios de selección
Define una constante para tu cláusula de selección, un array para contener argumentos de selección y una
variable para contener el valor de selección. Usa
la columna Contacts.LOOKUP_KEY para
encontrar 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 mediante la vinculación en lugar de la compilación de SQL. Este enfoque elimina la posibilidad de una inyección de SQL maliciosa.
Cómo definir un orden de clasificación
Define el orden de clasificación que quieras para el Cursor resultante. Para
mantener juntas todas las filas de un tipo de datos en particular, ordena por
Data.MIMETYPE. Este argumento de consulta
agrupa todas las filas de correo electrónico, todas las filas de teléfono, etcétera. 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 iterar a través del Cursor,
determinar el tipo de datos de la fila actual y almacenar datos para las filas que usan un subtipo. Cuando
termines de leer el cursor, podrás ordenar cada tipo de datos por subtipo y mostrar los
resultados.
Inicializa el cargador
Siempre realiza recuperaciones desde el proveedor de contactos (y todos los demás proveedores de contenido) en un
subproceso en segundo plano. Usa el framework del cargador definido por la
LoaderManager clase y la
LoaderManager.LoaderCallbacks interfaz para realizar recuperaciones en segundo plano.
Cuando todo esté listo para recuperar las filas, inicializa el framework del cargador llamando a
initLoader(). Pasa un
identificador entero al método; este identificador se pasa a los
LoaderManager.LoaderCallbacks métodos. 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 framework 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 onCreate(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; ... @Override public void onCreate(Bundle savedInstanceState) { ... // Initializes the loader framework getLoaderManager().initLoader(DETAILS_QUERY_ID, 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(). Muestra un
CursorLoader desde este método. Como buscas en la tabla ContactsContract.Data, usa la constante Data.CONTENT_URI como el 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 ); }
Cómo implementar onLoadFinished() y onLoaderReset()
Implementa el
onLoadFinished()
método. El framework del cargador llama a
onLoadFinished()
cuando el proveedor de contactos muestra los resultados de la consulta. 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 framework del cargador detecta que cambiaron los datos que respaldan el resultado
Cursor. En este punto, quita las referencias existentes
a la Cursor configurándolas como nulas. De lo contrario, el framework del cargador no destruirá el Cursor anterior y obtendrás 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; }
Cómo obtener detalles específicos de un contacto
Recuperar un tipo de datos específico para un contacto, como todos los correos electrónicos, sigue el mismo patrón que recuperar todos los detalles. Estos son los únicos cambios que debes realizar en el código que se muestra en Cómo obtener todos los detalles de un contacto:
- Proyección
-
Modifica tu proyección para recuperar las columnas que son específicas del
tipo de datos. También modifica la proyección para usar las constantes de nombre de columna definidas en la
ContactsContract.CommonDataKindssubclase correspondiente al tipo de datos. - Selección
-
Modifica el texto de selección para buscar el
MIMETYPEvalor que es específico de tu tipo de datos. - Ordenar por
-
Como solo seleccionas un tipo de detalle, no agrupes el
CursorporData.MIMETYPE.
Estas modificaciones se describen en las siguientes secciones.
Cómo definir una proyección
Define las columnas que deseas recuperar con las constantes de nombre de columna en la subclase
de ContactsContract.CommonDataKinds para el tipo de datos.
Si planeas vincular tu Cursor a un ListView,
asegúrate de recuperar la columna _ID. Por ejemplo, para recuperar 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 las columnas definidos en la clase
ContactsContract.CommonDataKinds.Email, en lugar de los nombres de las columnas
definidos en la clase ContactsContract.Data. Usar los nombres de las columnas específicas del correo electrónico
hace que el código sea más legible.
En la proyección, también puedes usar cualquiera de las otras columnas definidas en la
ContactsContract.CommonDataKinds subclase.
Cómo definir criterios de selección
Define una expresión de texto de búsqueda que recupere filas para la
LOOKUP_KEY de un contacto específico y el
Data.MIMETYPE de los detalles que
deseas. Encierra el valor MIMETYPE entre
comillas simples concatenando un "'" (comilla simple) al comienzo y al final
de la constante; de lo contrario, el proveedor interpreta la constante 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 usas una constante en lugar de un valor proporcionado por el usuario. 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 = { "" };
Cómo definir un orden de clasificación
Define un orden de clasificación para el Cursor resultante. Como recuperas un
tipo de datos específico, omite la ordenación en MIMETYPE.
En su lugar, si el tipo de datos detallados que buscas incluye un subtipo, ordénalos por subtipo.
Por ejemplo, para los datos de correo electrónico, puedes ordenar por
Email.TYPE:
Kotlin
private const val SORT_ORDER: String = "${Email.TYPE} ASC"
Java
private static final String SORT_ORDER = Email.TYPE + " ASC ";