Kontaktauswahl

Die Android-Kontaktauswahl ist eine standardisierte, durchsuchbare Benutzeroberfläche, über die Nutzer Kontakte für Ihre App freigeben können. Sie ist auf Geräten mit Android 17 (API-Level 37) oder höher verfügbar und bietet eine datenschutzfreundliche Alternative zur umfassenden Berechtigung READ_CONTACTS. Anstatt Zugriff auf das gesamte Adressbuch des Nutzers anzufordern, gibt Ihre App die benötigten Datenfelder an, z. B. Telefonnummern oder E-Mail-Adressen, und der Nutzer wählt bestimmte Kontakte aus, die freigegeben werden sollen. Dadurch erhält Ihre App nur Lesezugriff auf die ausgewählten Daten. Das sorgt für eine detaillierte Kontrolle und gleichzeitig für eine einheitliche Nutzererfahrung mit integrierter Suche, Profilwechsel und Mehrfachauswahl, ohne dass Sie die Benutzeroberfläche entwickeln oder verwalten müssen.

Kontaktauswahl einbinden

Verwenden Sie das Intent Intent.ACTION_PICK_CONTACTS, um die Kontaktauswahl zu integrieren. Mit diesem Intent wird die Auswahl gestartet und die ausgewählten Kontakte werden an Ihre App zurückgegeben.

Im Gegensatz zur alten ACTION_PICK können Sie mit der Kontaktauswahl mehrere Datenfelder angeben, die Ihre App gleichzeitig benötigt. Dazu verwenden Sie Intent.EXTRA_REQUESTED_DATA_FIELDS und übergeben eine ArrayList<String> von MIME-Typen, die in ContactsContract.CommonDataKinds definiert sind.

Häufige MIME-Typen sind:

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

Auswahl starten

Verwenden Sie registerForActivityResult mit dem StartActivityForResult-Vertrag, um die Auswahl zu starten. Sie können den Intent so konfigurieren, dass einzelne oder mehrere Auswahlen möglich sind.

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

Auswahlmodus

Die Benutzeroberfläche der Kontaktauswahl wird an die angeforderten Datenfelder angepasst. Je nach diesen Anforderungen können Nutzer entweder einen gesamten Kontaktdatensatz auswählen, wenn mehrere Felder benötigt werden, oder bestimmte Datenelemente aus den Informationen eines Kontakts auswählen.

Die verschiedenen UI-Modi der Kontaktauswahl
Abbildung 1. Die Benutzeroberfläche der Kontaktauswahl passt sich an die angeforderten Datenfelder an (Auswahl eines einzelnen Kontakts, mehrerer Kontakte und mehrerer Telefonnummern).

Einzelnen Kontakt auswählen

In diesem Beispiel fordert die App nur Telefonnummern an. Die Auswahl wird gefiltert, sodass nur Kontakte mit Telefonnummern angezeigt werden. Der Nutzer kann dann eine bestimmte Nummer auswählen.

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

Mehrere Kontakte auswählen

Fügen Sie das Intent.EXTRA_ALLOW_MULTIPLE-Extra hinzu, um die Mehrfachauswahl zu aktivieren. Optional können Sie die Anzahl der Elemente begrenzen, die ein Nutzer auswählen kann.

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)

Ergebnisse verarbeiten

Wenn der Nutzer die Auswahl abgeschlossen hat, gibt das System eine RESULT_OK und eine Sitzungs-URI zurück. Dieser URI gewährt temporären Lesezugriff auf die ausgewählten Daten.

Sie können diesen URI mit einem Standard-ContentResolver abfragen. Der resultierende Cursor enthält die angeforderten Datenfelder und entspricht dem Schema von 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()
}

Abwärtskompatibilität

Bei Apps, die auf Android 17 (API‑Level 37) und höher ausgerichtet sind, wird die vorhandene Intent.ACTION_PICK-Intention automatisch auf die neue Benutzeroberfläche für die Kontaktauswahl aktualisiert.

Wenn Ihre App bereits ACTION_PICK verwendet, müssen Sie Ihren Code nicht ändern, um die neue Benutzeroberfläche zu erhalten. Wenn Sie jedoch neue Funktionen wie den Empfang eines einzelnen Uri zum Abfragen von Kontaktdaten, das Umschalten zwischen privaten und Arbeitsprofilen oder mehrere Datenfeldanfragen nutzen möchten, müssen Sie Ihre Implementierung aktualisieren, damit Intent.ACTION_PICK_CONTACTS oder die neuen Intent-Extras verwendet werden.

Tests mit älteren Ziel-SDKs

Sie können das neue Auswahltool-Verhalten auf Geräten mit Android 17 und höher testen, auch wenn Ihre App auf eine niedrigere SDK-Version ausgerichtet ist. Fügen Sie dazu dem ACTION_PICK-Intent das boolesche Extra EXTRA_USE_SYSTEM_CONTACTS_PICKER hinzu.

Best Practices

  • Fordern Sie nur das an, was Sie benötigen: Wenn Ihre App nur eine SMS senden muss, fordern Sie Phone.CONTENT_ITEM_TYPE an. In der Auswahl werden Kontakte ohne Telefonnummer automatisch herausgefiltert, was zu einer übersichtlicheren Benutzeroberfläche für den Nutzer führt.
  • Mehrere Dateneinträge pro Kontakt verwalten: Einzelne Kontakte enthalten oft verschiedene E‑Mail-Adressen oder Telefonnummern. Damit diese Informationen für den Nutzer klar und intuitiv präsentiert werden, empfiehlt es sich, sie mit ContactsContract.Contacts.LOOKUP_KEY zu gruppieren. Außerdem können Sie für jeden Eintrag bestimmte Labels abrufen (z. B. „Arbeit“ oder „Privat“), um in der Benutzeroberfläche Ihrer App detailliertere Auswahlmöglichkeiten anzubieten.
  • Daten sofort speichern: Der Sitzungs-URI gewährt eine temporäre Leseberechtigung. Wenn Sie später (nachdem der App-Prozess beendet wurde) auf diese Kontaktdaten zugreifen müssen, muss Ihre App die Kontaktdaten speichern.
  • Nicht auf Kontodaten verlassen: Zum Schutz der Privatsphäre der Nutzer und zur Vermeidung von Fingerprinting werden kontospezifische Metadaten aus den Ergebnissen entfernt.