Selettore contatti

Il Selettore di contatti Android è un'interfaccia standardizzata e sfogliabile che consente agli utenti di condividere i contatti con la tua app. Disponibile sui dispositivi con Android 17 (livello API 37) o versioni successive, il selettore offre un'alternativa che tutela la privacy rispetto all'autorizzazione READ_CONTACTS generale. Anziché richiedere l'accesso all'intera rubrica dell'utente, la tua app specifica i campi dati di cui ha bisogno, ad esempio numeri di telefono o indirizzi email, e l'utente seleziona i contatti specifici da condividere. In questo modo, la tua app ottiene l'accesso in lettura solo ai dati selezionati, garantendo un controllo granulare e fornendo al contempo un'esperienza utente coerente con funzionalità di ricerca, cambio di profilo e selezione multipla integrate, senza dover creare o gestire l'interfaccia utente.

Integrare il Selettore di contatti

Per integrare il Selettore di contatti, utilizza l'intent Intent.ACTION_PICK_CONTACTS. Questo intent avvia il selettore e restituisce i contatti selezionati alla tua app.

A differenza dell'intent ACTION_PICK precedente, il Selettore di contatti ti consente di specificare più campi dati richiesti dalla tua app contemporaneamente. Per farlo, utilizza Intent.EXTRA_REQUESTED_DATA_FIELDS, passando un ArrayList<String> di tipi MIME definiti in ContactsContract.CommonDataKinds.

I tipi MIME comuni includono:

  • ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE
  • ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE
  • ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE

Avviare il selettore

Utilizza registerForActivityResult con il contratto StartActivityForResult per avviare il selettore. Puoi configurare l'intent in modo da consentire selezioni singole o multiple.

// 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)
        }
    }
}

Modalità di selezione

L'interfaccia utente del Selettore di contatti si adatta ai campi dati richiesti. A seconda di questi requisiti, gli utenti possono scegliere un intero record di contatto quando sono necessari più campi oppure selezionare elementi di dati specifici all'interno delle informazioni di un contatto.

Le diverse modalità della UI del selettore di contatti
Figura 1. L'interfaccia del Selettore di contatti si adatta ai campi dati richiesti (selezione di un singolo contatto, più contatti e più numeri di telefono).

Selezionare un singolo contatto

In questo esempio, l'app richiede solo i numeri di telefono. Il selettore filtrerà l'elenco in modo da mostrare solo i contatti con numeri di telefono e consentirà all'utente di selezionare un numero specifico.

// 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)

Selezionare più contatti

Per attivare la selezione multipla, aggiungi l'extra Intent.EXTRA_ALLOW_MULTIPLE. Facoltativamente, puoi limitare il numero di elementi che un utente può selezionare.

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)

Gestire i risultati

Quando l'utente completa la selezione, il sistema restituisce un RESULT_OK e un URI di sessione. Questo URI concede l'accesso in lettura temporaneo ai dati selezionati.

Puoi eseguire una query su questo URI utilizzando un ContentResolver standard. Il Cursor risultante contiene i campi dati richiesti e segue lo schema di 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()
}

Compatibilità con le versioni precedenti

Per le app che hanno come target Android 17 (livello API 37) e versioni successive, il sistema esegue automaticamente l'upgrade dell'intent Intent.ACTION_PICK esistente per utilizzare la nuova interfaccia del Selettore di contatti.

Se la tua app utilizza già ACTION_PICK, non devi modificare il codice per ricevere la nuova interfaccia utente. Tuttavia, per usufruire delle nuove funzionalità, ad esempio ricevere un singolo Uri per eseguire una query sui dati di contatto, passare da un profilo personale a un profilo di lavoro o richiedere più campi dati, devi aggiornare l'implementazione per utilizzare Intent.ACTION_PICK_CONTACTS o i nuovi extra dell'intent.

Testare gli SDK target precedenti

Puoi testare il nuovo comportamento del selettore sui dispositivi con Android 17 e versioni successive anche se la tua app ha come target una versione dell'SDK precedente aggiungendo l'extra booleano EXTRA_USE_SYSTEM_CONTACTS_PICKER all'intent ACTION_PICK.

Best practice

  • Richiedere solo ciò di cui hai bisogno: se la tua app deve solo inviare un SMS, richiedi Phone.CONTENT_ITEM_TYPE. Il selettore filtrerà automaticamente i contatti che non hanno numeri di telefono, con il risultato di un'interfaccia utente più pulita per l'utente.
  • Gestire più voci di dati per contatto: i singoli contatti spesso contengono vari indirizzi email o numeri di telefono. Per garantire che questi vengano presentati in modo chiaro e intuitivo per l'utente, ti consigliamo di raggrupparli utilizzando ContactsContract.Contacts.LOOKUP_KEY. Inoltre, puoi recuperare etichette specifiche per ogni voce (ad esempio lavoro o personale) per offrire opzioni di selezione più granulari all'interno dell'interfaccia della tua app.
  • Persistere immediatamente i dati: l'URI di sessione concede l'autorizzazione di lettura temporanea autorizzazione. Se devi accedere a queste informazioni di contatto in un secondo momento (dopo l'interruzione del processo dell'app), la tua app deve persistere i dati di contatto.
  • Non fare affidamento sui dati dell'account: per proteggere la privacy degli utenti e impedire il fingerprinting, i metadati specifici dell'account vengono rimossi dai risultati.