El Selector de contactos de Android es una interfaz estandarizada y navegable para que los usuarios compartan contactos con tu app. Disponible en dispositivos con Android 17 (nivel de API 37) o versiones posteriores, el selector ofrece una alternativa que preserva la privacidad al permiso amplio READ_CONTACTS. En lugar de solicitar acceso a toda la libreta de direcciones del usuario, tu app especifica los campos de datos que necesita, como números de teléfono o direcciones de correo electrónico, y el usuario selecciona contactos específicos para compartir. Esto le otorga a tu app acceso de lectura solo a los datos seleccionados, lo que garantiza un control detallado y, al mismo tiempo, proporciona una experiencia del usuario coherente con capacidades integradas de búsqueda, cambio de perfil y selección múltiple sin tener que compilar ni mantener la IU.
Integra el Selector de contactos
Para integrar el Selector de contactos, usa el intent Intent.ACTION_PICK_CONTACTS.
Este intent inicia el selector y muestra los contactos seleccionados en tu app.
A diferencia del ACTION_PICK heredado, el Selector de contactos te permite especificar varios campos de datos que requiere tu app al mismo tiempo. Para ello, usa
Intent.EXTRA_REQUESTED_DATA_FIELDS, pasando un ArrayList<String> de tipos MIME
definidos en ContactsContract.CommonDataKinds.
Entre los tipos MIME comunes, se incluyen los siguientes:
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPEContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPEContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE
Inicia el selector
Usa registerForActivityResult con el contrato StartActivityForResult para iniciar el selector. Puedes configurar el intent para permitir selecciones únicas o múltiples.
// Launcher for the Contact Picker intent
val pickContact = rememberLauncherForActivityResult(StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
val resultUri = it.data?.data ?: return@rememberLauncherForActivityResult
// Process the result URI in a background thread to fetch all selected contacts
coroutine.launch {
contacts = processContactPickerResultUri(resultUri, context)
}
}
}
Modo de selección
La IU del Selector de contactos se ajusta según los campos de datos solicitados. Según estos requisitos, los usuarios pueden elegir un registro de contacto completo cuando se necesitan varios campos o seleccionar elementos de datos específicos dentro de la información de un contacto.
Selecciona un solo contacto
En este ejemplo, la app solo solicita números de teléfono. El selector filtrará la lista para mostrar solo los contactos con números de teléfono y permitirá que el usuario seleccione un número específico.
// Define the specific contact data fields you need
val requestedFields = arrayListOf(
Email.CONTENT_ITEM_TYPE,
Phone.CONTENT_ITEM_TYPE,
)
// Set up the intent for the Contact Picker
val pickContactIntent = Intent(ACTION_PICK_CONTACTS).apply {
putStringArrayListExtra(
EXTRA_PICK_CONTACTS_REQUESTED_DATA_FIELDS,
requestedFields
)
}
// Launch the picker
pickContact.launch(pickContactIntent)
Selecciona varios contactos
Para habilitar la selección múltiple, agrega el extra Intent.EXTRA_ALLOW_MULTIPLE. De manera opcional, puedes limitar la cantidad de elementos que puede seleccionar un usuario.
val requestedFields = arrayListOf(
Email.CONTENT_ITEM_TYPE,
Phone.CONTENT_ITEM_TYPE,
)
// Set up the intent for the Contact Picker
val pickContactIntent = Intent(ACTION_PICK_CONTACTS).apply {
// Enable multi-select
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
// Set limit of selectable contacts
putExtra(EXTRA_PICK_CONTACTS_SELECTION_LIMIT, 5)
// Define the specific contact data fields you need
putStringArrayListExtra(
EXTRA_PICK_CONTACTS_REQUESTED_DATA_FIELDS,
requestedFields
)
// Enable this option to only filter contacts that have all the requested data fields
putExtra(EXTRA_PICK_CONTACTS_MATCH_ALL_DATA_FIELDS, false)
}
// Launch the picker
pickContact.launch(pickContactIntent)
Controla los resultados
Cuando el usuario completa la selección, el sistema muestra un RESULT_OK y un URI de sesión. Este URI otorga acceso de lectura temporal a los datos seleccionados.
Puedes consultar este URI con un ContentResolver estándar. El Cursor resultante contiene los campos de datos solicitados y sigue el esquema de ContactsContract.Data.
// Data class representing a parsed Contact with selected details.
data class Contact(
val lookupKey: String,
val name: String,
val emails: List<String>,
val phones: List<String>
)
// Helper function to query the content resolver with the URI returned by the Contact Picker.
// Parses the cursor to extract contact details such as name, email, and phone number.
private suspend fun processContactPickerResultUri(
sessionUri: Uri,
context: Context
): List<Contact> = withContext(Dispatchers.IO) {
// Define the columns we want to retrieve from the ContactPicker ContentProvider
val projection = arrayOf(
ContactsContract.Contacts.LOOKUP_KEY,
ContactsContract.Contacts.DISPLAY_NAME_PRIMARY,
ContactsContract.Data.MIMETYPE, // Type of data (e.g., email or phone)
ContactsContract.Data.DATA1, // The actual data (Phone number / Email string)
)
// We use `LOOKUP_KEY` as a unique ID to aggregate all contact info related to a same person
val contactsMap = mutableMapOf<String, Contact>()
// Note: The Contact Picker Session Uri doesn't support custom selection & selectionArgs.
// We query the URI directly to get the results chosen by the user.
context.contentResolver.query(sessionUri, projection, null, null, null)?.use { cursor ->
// Get the column indices for our requested projection
val lookupKeyIdx = cursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY)
val mimeTypeIdx = cursor.getColumnIndex(ContactsContract.Data.MIMETYPE)
val nameIdx = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY)
val data1Idx = cursor.getColumnIndex(ContactsContract.Data.DATA1)
while (cursor.moveToNext()) {
val lookupKey = cursor.getString(lookupKeyIdx)
val mimeType = cursor.getString(mimeTypeIdx)
val name = cursor.getString(nameIdx) ?: ""
val data1 = cursor.getString(data1Idx) ?: ""
val email = if (mimeType == Email.CONTENT_ITEM_TYPE) data1 else null
val phone = if (mimeType == Phone.CONTENT_ITEM_TYPE) data1 else null
val existingContact = contactsMap[lookupKey]
if (existingContact != null) {
contactsMap[lookupKey] = existingContact.copy(
emails = if (email != null) existingContact.emails + email else existingContact.emails,
phones = if (phone != null) existingContact.phones + phone else existingContact.phones
)
} else {
contactsMap[lookupKey] = Contact(
lookupKey = lookupKey,
name = name,
emails = if (email != null) listOf(email) else emptyList(),
phones = if (phone != null) listOf(phone) else emptyList()
)
}
}
}
return@withContext contactsMap.values.toList()
}
Retrocompatibilidad
En el caso de las apps orientadas a Android 17 (nivel de API 37) y versiones posteriores, el sistema actualiza automáticamente el intent Intent.ACTION_PICK existente para usar la nueva interfaz del Selector de contactos.
Si tu app ya usa ACTION_PICK, no es necesario que cambies el código para recibir la nueva IU. Sin embargo, para aprovechar las funciones nuevas, como recibir un solo Uri para consultar datos de contacto, cambiar entre perfiles personales y laborales o varias solicitudes de campos de datos, debes actualizar tu implementación para usar Intent.ACTION_PICK_CONTACTS o los nuevos extras de intent.
Pruebas en SDKs de destino anteriores
Puedes probar el nuevo comportamiento del selector en dispositivos con Android 17 y versiones posteriores, incluso si tu app está orientada a una versión inferior del SDK. Para ello, agrega el extra booleano EXTRA_USE_SYSTEM_CONTACTS_PICKER a tu intent ACTION_PICK.
Prácticas recomendadas
- Solicita solo lo que necesitas: Si tu app solo necesita enviar un SMS,
solicita
Phone.CONTENT_ITEM_TYPE. El selector filtrará automáticamente los contactos que no tengan números de teléfono, lo que generará una IU más limpia para el usuario. - Administra varias entradas de datos por contacto: Los contactos individuales suelen
contener varias direcciones de correo electrónico o números de teléfono. Para garantizar que se presenten de forma clara e intuitiva para el usuario, se recomienda agruparlos con
ContactsContract.Contacts.LOOKUP_KEY. Además, puedes recuperar etiquetas específicas para cada entrada (como laboral o personal) para ofrecer opciones de selección más detalladas dentro de la interfaz de tu app. - Haz que los datos persistan de inmediato: El URI de sesión otorga permiso de lectura temporal Si necesitas acceder a esta información de contacto más adelante (después de que se finalice el proceso de tu app), tu app debe hacer que los datos de contacto persistan.
- No dependas de los datos de la cuenta: Para proteger la privacidad del usuario y evitar la huella digital, se quitan los metadatos específicos de la cuenta de los resultados.