Selector de contactos: Uso compartido de contactos con prioridad para la privacidad
Lectura de 4 min
La privacidad y el control del usuario siguen siendo el centro de la experiencia de Android. Así como el selector de fotos hizo que el uso compartido de contenido multimedia fuera seguro y fácil de implementar, ahora llevamos ese mismo nivel de privacidad, simplicidad y excelente experiencia del usuario a la selección de contactos.
Un nuevo estándar para la privacidad de los contactos
Históricamente, las aplicaciones que requerían acceso a los contactos de un usuario específico dependían del permiso amplio READ_CONTACTS. Si bien era funcional, este enfoque a menudo otorgaba a las apps más datos de los necesarios. El nuevo Selector de contactos de Android, que se introdujo en Android 17, cambia esta dinámica, ya que proporciona una interfaz estandarizada, segura y con capacidad de búsqueda para la selección de contactos.
Esta función permite a los usuarios otorgar a las apps acceso solo a los contactos específicos que elijan, lo que se alinea con el compromiso de Android con la transparencia de los datos y las huellas de permisos minimizadas.
Cómo funciona
Los desarrolladores pueden integrar el Selector de contactos con el intent Intent.ACTION_PICK_CONTACTS. Esta API actualizada ofrece varias capacidades potentes:
- Solicitudes de datos detalladas: Las apps pueden especificar exactamente qué campos necesitan, como números de teléfono o direcciones de correo electrónico, en lugar de recibir el registro de contacto completo.
- Compatibilidad con la selección múltiple: El selector admite selecciones de contactos únicas y múltiples, lo que brinda a los desarrolladores más flexibilidad para funciones como las invitaciones grupales.
- Límites de selección: Los desarrolladores pueden establecer límites personalizados en la cantidad de contactos que un usuario puede seleccionar a la vez.
- Acceso temporal: Cuando se realiza la selección, el sistema muestra un URI de sesión que proporciona acceso de lectura temporal a los datos solicitados, lo que garantiza que el acceso no persista más de lo necesario.
- Acceso a otros perfiles: Cuando se usa este nuevo intent, la interfaz permitirá a los usuarios seleccionar contenido de otros perfiles de usuario, como un perfil de trabajo, un perfil clonado o un espacio privado.
- Rendimiento optimizado: El Selector de contactos muestra un solo URI que permite realizar consultas colectivas de resultados, lo que elimina la necesidad de consultar el URI de contacto individual por separado, como lo requiere
ACTION_PICK. Esta eficiencia reduce aún más la sobrecarga del sistema mediante el uso de una sola transacciónBinder.
Retrocompatibilidad e implementación
En los dispositivos que ejecutan Android 17 o versiones posteriores, el sistema actualiza automáticamente los intents ACTION_PICK heredados que especifican tipos de datos de contacto a la interfaz nueva y más segura. Sin embargo, para aprovechar al máximo las funciones avanzadas, como la selección múltiple, se recomienda a los desarrolladores que actualicen su código de implementación y utilicen ContentResolver para consultar el URI de sesión que se muestra.
Integra el selector de contactos. Para integrar el Selector de contactos, los desarrolladores usan el intent ACTION_PICK_CONTACTS. A continuación, se muestra un ejemplo de código que demuestra cómo iniciar el selector y solicitar campos de datos específicos, como el correo electrónico y los números de teléfono.
// State to hold the list of selected contacts var contacts by remember { mutableStateOf<List>(emptyList()) } // 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 coroutine.launch { contacts = processContactPickerResultUri(resultUri, context) } } } // 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 { putExtra(EXTRA_PICK_CONTACTS_SELECTION_LIMIT, 5) putStringArrayListExtra( EXTRA_PICK_CONTACTS_REQUESTED_DATA_FIELDS, requestedFields ) putExtra(EXTRA_PICK_CONTACTS_MATCH_ALL_DATA_FIELDS, false) } // Launch the picker pickContact.launch(pickContactIntent)
Después de que el usuario realiza una selección, la app procesa el resultado consultando el URI de sesión que se muestra para extraer la información de contacto solicitada.
// Data class representing a parsed Contact with selected details data class Contact(val id: String, val name: String, val email: String?, val phone: 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._ID, 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) ) val results = mutableListOf<Contact>() // Note: The Contact Picker Session Uri doesn't support custom selection & selectionArgs. context.contentResolver.query(sessionUri, projection, null, null, null)?.use { cursor -> // Get the column indices for our requested projection val contactIdIdx = cursor.getColumnIndex(ContactsContract.Contacts._ID) 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 contactId = cursor.getString(contactIdIdx) val mimeType = cursor.getString(mimeTypeIdx) val name = cursor.getString(nameIdx) ?: "" val data1 = cursor.getString(data1Idx) ?: "" // Determine if the current row represents an email or a phone number val email = if (mimeType == Email.CONTENT_ITEM_TYPE) data1 else null val phone = if (mimeType == Phone.CONTENT_ITEM_TYPE) data1 else null // Add the parsed contact to our results list results.add(Contact(contactId, name, email, phone)) } } return@withContext results }
Consulta la documentación completa aquí.
Prácticas recomendadas para desarrolladores
Para proporcionar la mejor experiencia del usuario y mantener altos estándares de seguridad, te recomendamos lo siguiente:
- Minimización de datos: Solicita solo los campos de datos específicos (p.ej., correo electrónico) que necesita tu app.
- Persistencia inmediata: Conserva los datos seleccionados de inmediato, ya que el acceso al URI de sesión es temporal.
-
Novedades de productosEl selector de fotos incorporado: Una forma más fluida de solicitar fotos y videos de forma privada en tu app.
Roxanna Aliabadi Walker, Yacine Rezgui • Lectura de 8 min -
Novedades de productosProporcionar una experiencia en línea segura y proteger a los usuarios de daños es una prioridad principal en Google Play.
Paul Feng • Lectura de 2 min -
Novedades de productosHoy, celebramos oficialmente cinco años desde el lanzamiento de Jetpack Compose 1.0. Desde la versión 1.0, anunciada el 28 de julio de 2021, hasta nuestra versión 1.11 más reciente, vimos que las APIs evolucionaron significativamente a lo largo de los años, y nos tomamos un momento para celebrarlo.
Rebecca Franks, Nick Butcher, Loryn Hairston • Lectura de 5 min
Recibe la información más reciente sobre el desarrollo de Android en tu bandeja de entrada todas las semanas.