O Seletor de Contatos do Android é uma interface padronizada e navegável para os usuários
compartilharem contatos com seu app. Disponível em dispositivos com
Android 17 (nível 37 da API) ou versões mais recentes, o seletor oferece uma alternativa
que preserva a privacidade em vez da permissão READ_CONTACTS abrangente. Em vez de solicitar acesso a toda a agenda de endereços do usuário, seu 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 controle granular e oferecendo uma experiência do usuário consistente com recursos integrados de pesquisa, troca de perfil 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 Intent.ACTION_PICK_CONTACTS.
Esse intent inicia o seletor e retorna os contatos selecionados para seu app.
Ao contrário do ACTION_PICK legado, o seletor de contatos permite especificar vários
campos de dados que seu app exige ao mesmo tempo. Para isso, use
Intent.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
Abrir o seletor
Use registerForActivityResult com o contrato StartActivityForResult para
iniciar o seletor. É possível configurar a intent para permitir uma ou várias
seleções.
// 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 filtra a lista para mostrar apenas os contatos com números de telefone e permite 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 {
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 {
// 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 de leitura temporário 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
Em apps direcionados 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 novos recursos, como
receber um único Uri para consultar dados de contato, alternar entre perfis pessoais e
de trabalho ou vários pedidos de campo de dados, atualize sua
implementação para usar Intent.ACTION_PICK_CONTACTS ou os novos extras de intent.
Testar em SDKs de destino mais antigos
Você pode testar o novo comportamento do seletor em dispositivos com Android 17 e
versões mais recentes, mesmo que o app segmente uma versão anterior do SDK, adicionando o
extra booleano EXTRA_USE_SYSTEM_CONTACTS_PICKER à intent ACTION_PICK.
Práticas recomendadas
- Solicite apenas o necessário: se o app só precisar 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. - Gerenciar 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 eles sejam apresentados de forma clara e intuitiva para o usuário, recomendamos agrupá-los usando
ContactsContract.Contacts.LOOKUP_KEY. Além disso, é possível recuperar rótulos específicos para cada entrada (como trabalho ou pessoal) e oferecer opções de seleção mais granulares na interface do app. - Persistir dados imediatamente: o URI da sessão concede permissão temporária de leitura. Se você precisar acessar essas informações 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 a impressão digital, os metadados específicos da conta são removidos dos resultados.