Selektor kontaktów na Androidzie to standardowy interfejs przeglądania, który umożliwia użytkownikom udostępnianie kontaktów w Twojej aplikacji. Jest dostępny na urządzeniach z Androidem 17 (interfejs API na poziomie 37) lub nowszym. Selektor stanowi alternatywę dla szerokiego uprawnienia READ_CONTACTS, która chroni prywatność. Zamiast prosić o dostęp do całej książki adresowej użytkownika, Twoja aplikacja określa pola danych, których potrzebuje, np. numery telefonów lub adresy e-mail, a użytkownik wybiera konkretne kontakty do udostępnienia. Dzięki temu Twoja aplikacja ma dostęp do odczytu tylko wybranych danych, co zapewnia szczegółową kontrolę, a jednocześnie spójne wrażenia użytkowników dzięki wbudowanym funkcjom wyszukiwania, przełączania profili i wielokrotnego wyboru bez konieczności tworzenia i utrzymywania interfejsu.
Integracja selektora kontaktów
Aby zintegrować selektor kontaktów, użyj intencji Intent.ACTION_PICK_CONTACTS.
Ta intencja uruchamia selektor i zwraca wybrane kontakty do aplikacji.
W przeciwieństwie do starszej intencji ACTION_PICK selektor kontaktów umożliwia jednoczesne określenie wielu pól danych wymaganych przez aplikację. Możesz to zrobić za pomocą
Intent.EXTRA_REQUESTED_DATA_FIELDS, przekazując ArrayList<String> typów MIME
zdefiniowanych w ContactsContract.CommonDataKinds.
Typowe typy MIME to:
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPEContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPEContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE
Uruchamianie selektora
Aby uruchomić selektor, użyj registerForActivityResult z kontraktem StartActivityForResult. 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.
Wybieranie pojedynczego kontaktu
W tym przykładzie aplikacja prosi tylko o numery telefonów. Selektor odfiltruje listę, aby wyświetlać 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 dodatkowy element Intent.EXTRA_ALLOW_MULTIPLE. Opcjonalnie możesz 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 zakończy wybieranie, system zwróci RESULT_OK i identyfikator URI sesji. Ten identyfikator URI przyznaje tymczasowy dostęp do odczytu wybranych danych.
Możesz wysłać zapytanie do tego identyfikatora URI za pomocą standardowego ContentResolver. Wynikowy Cursor zawiera żądane pola danych i jest zgodny ze schematem 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 (interfejs API na poziomie 37) lub nowszego system automatycznie uaktualnia istniejącą intencję Intent.ACTION_PICK, aby korzystać z nowego interfejsu selektora kontaktów.
Jeśli Twoja aplikacja używa już ACTION_PICK, nie musisz zmieniać kodu, aby otrzymywać 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 osobistymi i służbowymi czy wysyłanie zapytań o wiele pól 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 lub nowszym, nawet jeśli Twoja aplikacja jest kierowana na starszą wersję pakietu SDK. Wystarczy, że dodasz do intencji ACTION_PICK dodatkowy element logiczny EXTRA_USE_SYSTEM_CONTACTS_PICKER.
Sprawdzone metody
- Proś tylko o to, czego potrzebujesz: jeśli Twoja aplikacja musi tylko wysłać SMS-a,
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 na kontakt: poszczególne kontakty często
zawierają różne adresy e-mail lub numery telefonów. Aby zapewnić, że są one prezentowane użytkownikowi w przejrzysty i intuicyjny sposób, zalecamy grupowanie ich za pomocą
ContactsContract.Contacts.LOOKUP_KEY. Ponadto możesz pobrać 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 uprawnienie do odczytu. Jeśli chcesz uzyskać dostęp do tych informacji kontaktowych później (po zamknięciu procesu aplikacji), Twoja aplikacja musi utrwalić dane kontaktowe.
- Nie polegaj na danych konta: aby chronić prywatność użytkowników i zapobiegać tworzeniu odcisków cyfrowych, metadane specyficzne dla konta są usuwane z wyników.