Le Sélecteur de Contacts Android est une interface standardisée et navigable qui permet aux utilisateurs de partager des contacts avec votre application. Disponible sur les appareils fonctionnant sous Android 17 (niveau d'API 37) ou version ultérieure, il offre une alternative respectueuse de la confidentialité à l'autorisation READ_CONTACTS étendue. Au lieu de demander l'accès à l'intégralité du carnet d'adresses de l'utilisateur, votre application spécifie les champs de données dont elle a besoin, tels que les numéros de téléphone ou les adresses e-mail, et l'utilisateur sélectionne les contacts spécifiques à partager. Cela accorde à votre application un accès en lecture uniquement aux données sélectionnées, ce qui garantit un contrôle précis tout en offrant une expérience utilisateur cohérente avec des fonctionnalités de recherche, de changement de profil et de sélection multiple intégrées, sans avoir à créer ni à gérer l'UI.
Intégrer le sélecteur de contacts
Pour intégrer le sélecteur de contacts, utilisez l'intent Intent.ACTION_PICK_CONTACTS.
Cette intention lance le sélecteur et renvoie les contacts sélectionnés à votre application.
Contrairement à l'ancien ACTION_PICK, le sélecteur de contacts vous permet de spécifier plusieurs champs de données requis par votre application en même temps. Pour ce faire, utilisez Intent.EXTRA_REQUESTED_DATA_FIELDS en transmettant un ArrayList<String> de types MIME définis dans ContactsContract.CommonDataKinds.
Voici quelques types MIME courants :
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPEContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPEContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE
Lancer le sélecteur
Utilisez registerForActivityResult avec le contrat StartActivityForResult pour lancer le sélecteur. Vous pouvez configurer l'intention pour autoriser une ou plusieurs sélections.
// 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)
}
}
}
Mode Sélection
L'UI du sélecteur de contacts s'adapte en fonction des champs de données demandés. En fonction de ces exigences, les utilisateurs peuvent choisir un enregistrement de contact entier lorsque plusieurs champs sont nécessaires, ou sélectionner des éléments de données spécifiques dans les informations d'un contact.
Sélectionner un seul contact
Dans cet exemple, l'application ne demande que les numéros de téléphone. Le sélecteur filtrera la liste pour n'afficher que les contacts avec des numéros de téléphone et permettra à l'utilisateur de sélectionner un numéro spécifique.
// 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)
Sélectionner plusieurs contacts
Pour activer la sélection multiple, ajoutez l'extra Intent.EXTRA_ALLOW_MULTIPLE. Vous pouvez éventuellement limiter le nombre d'éléments qu'un utilisateur peut sélectionner.
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)
Gérer les résultats
Lorsque l'utilisateur termine la sélection, le système renvoie un RESULT_OK et un URI de session. Cet URI accorde un accès en lecture temporaire aux données sélectionnées.
Vous pouvez interroger cet URI à l'aide d'un ContentResolver standard. Le Cursor obtenu contient les champs de données demandés et suit le schéma 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()
}
Rétrocompatibilité
Pour les applications ciblant Android 17 (niveau d'API 37) et versions ultérieures, le système met automatiquement à niveau l'intent Intent.ACTION_PICK existant pour utiliser la nouvelle interface du sélecteur de contacts.
Si votre application utilise déjà ACTION_PICK, vous n'avez pas besoin de modifier votre code pour recevoir la nouvelle UI. Toutefois, pour profiter des nouvelles fonctionnalités, comme recevoir un seul Uri pour interroger les données de contact, basculer entre les profils personnel et professionnel, ou effectuer plusieurs demandes de champs de données, vous devez mettre à jour votre implémentation pour utiliser Intent.ACTION_PICK_CONTACTS ou les nouveaux extras d'intent.
Tester sur des SDK cibles plus anciens
Vous pouvez tester le nouveau comportement du sélecteur sur les appareils exécutant Android 17 ou version ultérieure, même si votre application cible une version du SDK inférieure, en ajoutant l'extra booléen EXTRA_USE_SYSTEM_CONTACTS_PICKER à votre intent ACTION_PICK.
Bonnes pratiques
- Ne demandez que ce dont vous avez besoin : si votre application n'a besoin que d'envoyer un SMS, demandez
Phone.CONTENT_ITEM_TYPE. Le sélecteur filtrera automatiquement les contacts qui n'ont pas de numéro de téléphone, ce qui permettra à l'utilisateur de bénéficier d'une interface utilisateur plus claire. - Gérer plusieurs entrées de données par contact : les contacts individuels contiennent souvent plusieurs adresses e-mail ou numéros de téléphone. Pour vous assurer que ces éléments sont présentés de manière claire et intuitive pour l'utilisateur, il est recommandé de les regrouper à l'aide de
ContactsContract.Contacts.LOOKUP_KEY. De plus, vous pouvez récupérer des libellés spécifiques pour chaque entrée (comme "professionnel" ou "personnel") afin d'offrir des options de sélection plus précises dans l'interface de votre application. - Persister les données immédiatement : l'URI de session accorde une autorisation de lecture temporaire. Si vous devez accéder à ces coordonnées ultérieurement (après l'arrêt du processus de votre application), votre application doit conserver les données de contact.
- Ne vous fiez pas aux données de compte : pour protéger la confidentialité des utilisateurs et éviter l'empreinte numérique, les métadonnées spécifiques au compte sont supprimées des résultats.