Selektor kontaktów na Androidzie to ustandaryzowany, możliwy do przeglądania interfejs, który umożliwia użytkownikom udostępnianie kontaktów aplikacji. Jest on dostępny na urządzeniach z Androidem 17 (interfejs API na poziomie 37) lub nowszym i stanowi chroniącą prywatność alternatywę dla szerokiego uprawnienia READ_CONTACTS. Zamiast prosić o dostęp do całej książki adresowej użytkownika, aplikacja określa pola danych, których potrzebuje, takie jak numery telefonów lub adresy e-mail, a użytkownik wybiera konkretne kontakty do udostępnienia. Dzięki temu aplikacja ma dostęp do odczytu tylko wybranych danych, co zapewnia szczegółową kontrolę i spójne wrażenia użytkownika dzięki wbudowanym funkcjom wyszukiwania, przełączania profili w wyszukiwarce i wyboru wielu elementów bez konieczności tworzenia i utrzymywania interfejsu.
Integracja selektora kontaktów
Aby zintegrować selektor kontaktów, użyj intencji ContactsPickerSessionContract.ACTION_PICK_CONTACTS.
Uruchamia ona 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ą ContactsPickerSessionContract.EXTRA_REQUESTED_DATA_FIELDS, przekazując ArrayList<String> typów MIME zdefiniowanych w ContactsContract.CommonDataKinds.
Najczęstsze typy MIME:
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 wybór jednego lub wielu elementów.
// 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 konkretne elementy danych z informacji o kontakcie.
Wybieranie jednego kontaktu
W tym przykładzie aplikacja prosi tylko o numery telefonów. Selektor przefiltruje listę, aby wyświetlić tylko kontakty z numerami telefonów, i pozwoli użytkownikowi wybrać konkretny numer.
// 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_USE_SYSTEM_CONTACTS_PICKER, true)
putStringArrayListExtra(
EXTRA_PICK_CONTACTS_REQUESTED_DATA_FIELDS,
requestedFields
)
}
// Launch the picker
pickContact.launch(pickContactIntent)
Zaznaczanie kilku kontaktów
Aby włączyć wielokrotny wybór, dodaj Intent.EXTRA_ALLOW_MULTIPLE. 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 {
putExtra(EXTRA_USE_SYSTEM_CONTACTS_PICKER, true)
// 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)
Przetwarzanie wyników
Gdy użytkownik dokona wyboru, system zwraca 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 obiekt 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 (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 korzysta już z ACTION_PICK, nie musisz zmieniać kodu, aby uzyskać nowy interfejs. Aby jednak korzystać z nowych funkcji, takich jak otrzymywanie pojedynczego obiektu Uri do wysyłania zapytań o dane kontaktowe, przełączanie się między profilami osobistymi i służbowymi czy wysyłanie wielu żądań dotyczących pól danych, musisz zaktualizować implementację, aby używać ContactsPickerSessionContract.ACTION_PICK_CONTACTS lub nowych dodatków intencji.
Testowanie na starszych docelowych pakietach SDK
Nowe działanie selektora możesz przetestować na urządzeniach z Androidem 17 lub nowszym, nawet jeśli aplikacja jest kierowana na starszą wersję pakietu SDK. W tym celu dodaj do intencji ACTION_PICK wartość logiczną EXTRA_USE_SYSTEM_CONTACTS_PICKER.
Sprawdzone metody
- Proś tylko o to, czego potrzebujesz: jeśli 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 bardziej 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 sposób przejrzysty i intuicyjny, zalecamy pogrupowanie ich za pomocą elementu
ContactsContract.Contacts.LOOKUP_KEY. Możesz też pobierać konkretne etykiety dla każdego wpisu (np. praca lub osobiste), aby oferować bardziej szczegółowe opcje wyboru w interfejsie aplikacji. - Natychmiastowe utrwalanie danych: identyfikator URI sesji przyznaje tymczasowe uprawnienia do odczytu. Jeśli chcesz uzyskać dostęp do tych informacji kontaktowych później (po zakończeniu procesu aplikacji), aplikacja musi zachować dane kontaktowe.
- Nie polegaj na danych na koncie: aby chronić prywatność użytkownika i zapobiegać sygnaturze przeglądarki, z wyników usuwane są metadane dotyczące konta.