Инструмент выбора контактов в Android – это стандартизированный интерфейс, в котором пользователи могут просматривать контакты и делиться ими с вашим приложением. Он доступен на устройствах с Android 17 (уровень API 37) или более поздней версии и представляет собой альтернативу разрешению READ_CONTACTS, которая обеспечивает конфиденциальность. Вместо того чтобы запрашивать доступ ко всей адресной книге пользователя, ваше приложение указывает, какие поля данных ему нужны, например номера телефонов или адреса электронной почты, а пользователь выбирает, какими контактами поделиться. При этом приложение получит доступ только к выбранным данным. Это позволит вам точно управлять доступом и обеспечивать удобство работы пользователей благодаря встроенным функциям поиска, переключения профилей и выбора нескольких объектов без необходимости создавать и поддерживать интерфейс.
Как интегрировать инструмент выбора контактов
Чтобы интегрировать инструмент выбора контактов, используйте интент ContactsPickerSessionContract.ACTION_PICK_CONTACTS.
Оно запускает инструмент и предоставляет приложению доступ к выбранным контактам.
В отличие от устаревшего намерения ACTION_PICK, инструмент выбора контактов позволяет одновременно указать несколько полей данных, необходимых вашему приложению. Для этого используется функция
ContactsPickerSessionContract.EXTRA_REQUESTED_DATA_FIELDS, в которую передается
ArrayList<String> MIME-типов, определенных в ContactsContract.CommonDataKinds.
К стандартным MIME-типам относятся:
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPEContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPEContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE
Как запустить окно выбора
Используйте registerForActivityResult с контрактом StartActivityForResult, чтобы запустить выбор. Вы можете настроить намерение так, чтобы можно было выбрать один или несколько вариантов.
// 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)
}
}
}
Режим выбора
Интерфейс выбора контактов адаптируется в зависимости от запрошенных полей данных. В зависимости от этих требований пользователи могут выбрать всю запись о контакте, если нужно несколько полей, или отдельные элементы данных из информации о контакте.
Как выбрать один контакт
В этом примере приложение запрашивает только номера телефонов. В списке будут только контакты с номерами телефонов, и пользователь сможет выбрать нужный номер.
// 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)
Как выбрать несколько контактов
Чтобы включить возможность выбора нескольких вариантов, добавьте параметр Intent.EXTRA_ALLOW_MULTIPLE. При необходимости вы можете ограничить количество объектов, которые может выбрать пользователь.
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)
Как обрабатывать результаты
Когда пользователь завершит выбор, система вернет RESULT_OK и URI сеанса. Этот URI предоставляет временный доступ на чтение к выбранным данным.
Вы можете запросить этот URI, используя стандартный метод ContentResolver. В результате будет получен объект Cursor, содержащий запрошенные поля данных и соответствующий схеме 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()
}
Обратная совместимость
В приложениях, предназначенных для Android 17 (уровень API 37) и более поздних версий, намерение Intent.ACTION_PICK обновится автоматически для поддержки интерфейса нового инструмента выбора контактов.
Если в вашем приложении уже используется ACTION_PICK, вам не нужно менять код, чтобы получить новый интерфейс. Однако, чтобы использовать новые функции, например получать один Uri для запроса данных о контактах, переключаться между личным и рабочим профилями или запрашивать несколько полей данных, вам нужно обновить реализацию, чтобы использовать ContactsPickerSessionContract.ACTION_PICK_CONTACTS или новые дополнительные параметры намерения.
Тестирование на более старых целевых SDK
Вы можете протестировать новый принцип работы средства выбора на устройствах с Android 17 и более поздних версий, даже если ваше приложение предназначено для более ранней версии SDK. Для этого добавьте логическое значение EXTRA_USE_SYSTEM_CONTACTS_PICKER в намерение ACTION_PICK.
Рекомендации
- Запрашивайте только то, что вам нужно. Если приложению нужно только отправлять SMS, запросите разрешение
Phone.CONTENT_ITEM_TYPE. Выборщик автоматически отфильтрует контакты без номеров телефонов, что сделает интерфейс более удобным. - Управление несколькими записями данных для одного контакта. У одного контакта часто бывает несколько адресов электронной почты или номеров телефонов. Чтобы пользователю было проще их воспринимать, рекомендуется сгруппировать их с помощью тега
ContactsContract.Contacts.LOOKUP_KEY. Кроме того, вы можете получать определенные ярлыки для каждой записи (например, "Работа" или "Личное"), чтобы предлагать более точные варианты выбора в интерфейсе приложения. - Сохранять данные немедленно. URI сеанса предоставляет временное разрешение на чтение. Если вам понадобится доступ к этой контактной информации позже (после того, как процесс приложения будет завершен), ваше приложение должно сохранить данные о контакте.
- Не полагайтесь на данные аккаунта. Чтобы защитить конфиденциальность пользователей и предотвратить сбор отпечатков браузера, из результатов поиска удаляются метаданные, относящиеся к аккаунту.