Selektor kontaktów

Selektor kontaktów na Androidzie to standardowy interfejs, w którym użytkownicy mogą przeglądać i udostępniać kontakty w Twojej aplikacji. Jest on dostępny na urządzeniach z Androidem 17 (API na poziomie 37) lub nowszym i stanowi alternatywę dla szerokiego uprawnienia READ_CONTACTS, która chroni prywatność. Zamiast prosić o dostęp do całej książki adresowej użytkownika, aplikacja określa potrzebne pola danych, takie jak numery telefonów lub adresy e-mail, a użytkownik wybiera konkretne kontakty do udostępnienia. Dzięki temu aplikacja będzie miała dostęp do odczytu tylko wybranych danych, co zapewni szczegółową kontrolę i spójne wrażenia użytkowników dzięki wbudowanym funkcjom wyszukiwania, przełączania profili i wielokrotnego wyboru bez konieczności tworzenia interfejsu użytkownika ani zarządzania nim.

Integracja selektora kontaktów

Aby zintegrować selektor kontaktów, użyj intencji Intent.ACTION_PICK_CONTACTS. Ten zamiar uruchamia selektor i zwraca wybrane kontakty do aplikacji.

W przeciwieństwie do starszego selektora ACTION_PICK selektor kontaktów umożliwia jednoczesne określanie wielu pól danych wymaganych przez aplikację. Możesz to zrobić za pomocą elementu Intent.EXTRA_REQUESTED_DATA_FIELDS, przekazując ArrayList<String> typów MIME zdefiniowanych w ContactsContract.CommonDataKinds.

Najczęstsze typy MIME to:

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

Uruchamianie selektora

Użyj registerForActivityResult z umową StartActivityForResult, aby uruchomić selektor. Możesz skonfigurować intencję tak, aby zezwalała na pojedynczy lub wielokrotny wybór.

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

Tryb wyboru

Interfejs selektora kontaktów dostosowuje się do żądanych pól danych. W zależności od tych wymagań użytkownicy mogą wybrać cały rekord kontaktu, gdy potrzebnych jest wiele pól, lub wybrać konkretne elementy danych z informacji o kontakcie.

Różne tryby interfejsu selektora kontaktów
Rysunek 1. Interfejs selektora kontaktów dostosowuje się do żądanych pól danych (wybór pojedynczego kontaktu, wielu kontaktów i wielu numerów telefonów).

Wybieranie pojedynczego kontaktu

W tym przykładzie aplikacja prosi tylko o numery telefonów. Selektor odfiltruje listę, aby wyświetlić tylko kontakty z numerami telefonów, i umożliwi użytkownikowi wybranie konkretnego numeru.

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

Wybieranie wielu kontaktów

Aby włączyć wielokrotny wybór, dodaj Intent.EXTRA_ALLOW_MULTIPLE extra. Możesz opcjonalnie ograniczyć liczbę elementów, które użytkownik może wybrać.

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)

Obsługa wyników

Gdy użytkownik dokona wyboru, system zwraca wartość RESULT_OK i adres URI sesji. Ten identyfikator URI przyznaje tymczasowy dostęp do odczytu wybranych danych.

Możesz wysyłać zapytania do tego identyfikatora URI za pomocą standardowego ContentResolver. Wynikowy plik Cursor zawiera żądane pola danych i jest zgodny ze schematem pliku 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()
}

Zgodność wsteczna

W przypadku aplikacji kierowanych na Androida 17 (API na poziomie 37) i nowsze wersje system automatycznie uaktualnia istniejący zamiar Intent.ACTION_PICK, aby korzystać z nowego interfejsu selektora kontaktów.

Jeśli Twoja aplikacja korzysta już z ACTION_PICK, nie musisz zmieniać kodu, aby otrzymać nowy interfejs. Aby jednak korzystać z nowych funkcji, takich jak otrzymywanie pojedynczego Uri do wysyłania zapytań o dane kontaktowe, przełączanie się między profilami osobistym i służbowym czy wysyłanie wielu próśb o pola danych, musisz zaktualizować implementację, aby używać Intent.ACTION_PICK_CONTACTS lub nowych dodatków do intencji.

Testowanie na starszych docelowych pakietach SDK

Możesz przetestować nowe działanie selektora na urządzeniach z Androidem 17 i nowszym, nawet jeśli Twoja aplikacja jest kierowana na starszą wersję pakietu SDK. Wystarczy, że dodasz do intencji ACTION_PICK dodatkowy parametr logiczny EXTRA_USE_SYSTEM_CONTACTS_PICKER.

Sprawdzone metody

  • Proś tylko o to, czego potrzebujesz: jeśli Twoja aplikacja musi tylko wysyłać SMS-y, poproś o Phone.CONTENT_ITEM_TYPE. Selektor automatycznie odfiltruje kontakty, które nie mają numerów telefonów, co zapewni użytkownikowi przejrzysty interfejs.
  • Zarządzanie wieloma wpisami danych dla jednego kontaktu: poszczególne kontakty często zawierają różne adresy e-mail lub numery telefonów. Aby zapewnić użytkownikowi jasne i intuicyjne wyświetlanie tych informacji, zalecamy grupowanie ich za pomocą znaku ContactsContract.Contacts.LOOKUP_KEY. Możesz też pobierać konkretne etykiety dla każdego wpisu (np. służbowy lub osobisty), aby oferować bardziej szczegółowe opcje wyboru w interfejsie aplikacji.
  • Natychmiastowe utrwalanie danych: identyfikator URI sesji przyznaje tymczasowe uprawnienia do odczytu. Jeśli chcesz później uzyskać dostęp do tych informacji kontaktowych (po zamknięciu procesu aplikacji), musisz zapisać dane kontaktowe.
  • Nie polegaj na danych konta: aby chronić prywatność użytkowników i zapobiegać tworzeniu odcisków cyfrowych, z wyników usuwane są metadane dotyczące konta.