O Seletor de Contatos do Android é uma interface padronizada e navegável para que os usuários
compartilhem contatos com seu app. Disponível em dispositivos com
Android 17 (nível 37 da API) ou mais recente, o seletor oferece uma alternativa que preserva a privacidade
à permissão ampla READ_CONTACTS. Em vez de solicitar
acesso a toda a lista de endereços do usuário, o app especifica os campos de dados necessários, como números de telefone ou endereços de e-mail, e o usuário seleciona contatos
específicos para compartilhar. Isso concede ao app acesso de leitura apenas aos dados selecionados,
garantindo um controle granular e oferecendo uma experiência do usuário consistente com
recursos integrados de pesquisa, troca de perfil na Pesquisa e seleção múltipla sem
precisar criar ou manter a interface.
Integrar o seletor de contatos
Para integrar o seletor de contatos, use a
intent ContactsPickerSessionContract.ACTION_PICK_CONTACTS.
Essa intent inicia o seletor e retorna os contatos selecionados para o app.
Ao contrário do ACTION_PICK legado, o seletor de contatos permite especificar vários
campos de dados que o app exige ao mesmo tempo. Para isso, use
ContactsPickerSessionContract.EXTRA_REQUESTED_DATA_FIELDS, transmitindo um
ArrayList<String> de tipos MIME definidos em ContactsContract.CommonDataKinds.
Os tipos MIME comuns incluem:
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPEContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPEContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE
Iniciar o seletor
Use registerForActivityResult com o contrato StartActivityForResult para
iniciar o seletor. É possível configurar a intent para permitir seleções únicas ou
múltiplas.
// 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)
}
}
}
Modo de seleção
A interface do Seletor de Contatos se ajusta de acordo com os campos de dados solicitados. Dependendo desses requisitos, os usuários podem escolher um registro de contato inteiro quando vários campos são necessários ou selecionar itens de dados específicos nas informações de um contato.
Selecionar um único contato
Neste exemplo, o app solicita apenas números de telefone. O seletor vai filtrar a lista para mostrar apenas os contatos com números de telefone e permitir que o usuário selecione um número específico.
// 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)
Selecionar vários contatos
Para ativar a seleção múltipla, adicione o extra Intent.EXTRA_ALLOW_MULTIPLE. Você pode
limitar o número de itens que um usuário pode selecionar.
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)
Processar os resultados
Quando o usuário conclui a seleção, o sistema retorna um RESULT_OK e um
URI de sessão. Esse URI concede acesso temporário de leitura aos dados selecionados.
É possível consultar esse URI usando um ContentResolver padrão. O Cursor resultante contém os campos de dados solicitados e segue o esquema de 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()
}
Compatibilidade com versões anteriores
Para apps destinados ao Android 17 (nível 37 da API) e versões mais recentes, o sistema
atualiza automaticamente a intent Intent.ACTION_PICK para usar a nova
interface do seletor de contatos.
Se o app já usa ACTION_PICK, não é necessário mudar o código para
receber a nova interface. No entanto, para aproveitar os novos recursos, como
receber um único Uri para consultar dados de contato, alternar entre perfis pessoais e
de trabalho ou várias solicitações de campos de dados, é necessário atualizar sua
implementação para usar ContactsPickerSessionContract.ACTION_PICK_CONTACTS ou
os novos extras de intent.
Como testar em SDKs de destino mais antigos
É possível testar o novo comportamento do seletor em dispositivos com o Android 17 e
versões mais recentes, mesmo que o app seja destinado a uma versão anterior do SDK, adicionando o
booleano extra EXTRA_USE_SYSTEM_CONTACTS_PICKER à intent ACTION_PICK.
Práticas recomendadas
- Solicite apenas o que for necessário: se o app só precisa enviar um SMS,
solicite
Phone.CONTENT_ITEM_TYPE. O seletor vai filtrar automaticamente os contatos que não têm números de telefone, resultando em uma interface mais limpa para o usuário. - Gerenciamento de várias entradas de dados por contato: os contatos individuais geralmente
contêm vários endereços de e-mail ou números de telefone. Para garantir que elas sejam
apresentadas de forma clara e intuitiva para o usuário, recomendamos agrupá-las
usando
ContactsContract.Contacts.LOOKUP_KEY. Além disso, é possível recuperar rótulos específicos para cada entrada (como trabalho ou pessoal) para oferecer opções de seleção mais granulares na interface do app. - Persistir dados imediatamente: o URI da sessão concede permissão de leitura temporária. Se você precisar acessar esses dados de contato mais tarde (depois que o processo do app for encerrado), o app precisará manter os dados de contato.
- Não dependa dos dados da conta: para proteger a privacidade do usuário e evitar impressões digitais, os metadados específicos da conta são removidos dos resultados.