כלי בחירת אנשי הקשר ב-Android הוא ממשק סטנדרטי שמאפשר למשתמשים לשתף אנשי קשר עם האפליקציה שלכם. הכלי זמין במכשירים עם Android 17 (API level 37) ואילך, והוא מציע חלופה ששומרת על הפרטיות להרשאת הגישה הרחבה READ_CONTACTS. במקום לבקש גישה לכל פנקס הכתובות של המשתמש, האפליקציה מציינת את שדות הנתונים שהיא צריכה, כמו מספרי טלפון או כתובות אימייל, והמשתמש בוחר אנשי קשר ספציפיים לשיתוף. ההרשאה הזו מעניקה לאפליקציה גישת קריאה רק לנתונים שנבחרו, וכך מאפשרת שליטה מפורטת תוך שמירה על חוויית משתמש עקבית עם יכולות מובנות של חיפוש, מעבר בין פרופילים ובחירה מרובה, בלי שתצטרכו ליצור או לתחזק את ממשק המשתמש.
שילוב הכלי לבחירת אנשי קשר
כדי לשלב את כלי בחירת אנשי הקשר, משתמשים בכוונה Intent.ACTION_PICK_CONTACTS.
ה-Intent הזה מפעיל את הכלי לבחירת אנשי קשר ומחזיר את אנשי הקשר שנבחרו לאפליקציה.
בניגוד לACTION_PICK מדור קודם, הכלי לבחירת אנשי קשר מאפשר לציין כמה שדות נתונים שהאפליקציה דורשת בו-זמנית. כדי לעשות את זה, משתמשים ב-Intent.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 {
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 {
// 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 הקיים Intent.ACTION_PICK כדי להשתמש בממשק החדש של הכלי לבחירת אנשי קשר.
אם האפליקציה כבר משתמשת ב-ACTION_PICK, לא צריך לשנות את הקוד כדי לקבל את ממשק המשתמש החדש. עם זאת, כדי ליהנות מתכונות חדשות, כמו קבלת Uri יחיד לשאילתת נתוני אנשי קשר, מעבר בין פרופילים אישיים ופרופילים של מקומות עבודה או בקשות מרובות של שדות נתונים, צריך לעדכן את ההטמעה לשימוש ב-Intent.ACTION_PICK_CONTACTS או בתוספים החדשים של הכוונות.
בדיקה בגרסאות ישנות יותר של SDK יעד
אתם יכולים לבדוק את ההתנהגות החדשה של הכלי לבחירת קבצים במכשירים עם Android 17 ומעלה, גם אם האפליקציה שלכם מיועדת לגרסת SDK נמוכה יותר. לשם כך, צריך להוסיף את הערך הבוליאני EXTRA_USE_SYSTEM_CONTACTS_PICKER extra ל-intent של ACTION_PICK.
שיטות מומלצות
- מבקשים רק את מה שצריך: אם האפליקציה שלכם צריכה רק לשלוח SMS, בקשו את ההרשאה
Phone.CONTENT_ITEM_TYPE. הכלי לבחירת אנשי קשר יסנן באופן אוטומטי אנשי קשר שאין להם מספרי טלפון, וכך הממשק יהיה נקי יותר למשתמש. - ניהול של כמה רשומות נתונים לכל איש קשר: לעיתים קרובות, אנשי קשר בודדים מכילים כתובות אימייל או מספרי טלפון שונים. כדי לוודא שהם מוצגים בצורה ברורה ואינטואיטיבית למשתמש, מומלץ לקבץ אותם באמצעות
ContactsContract.Contacts.LOOKUP_KEY. בנוסף, תוכלו לאחזר תוויות ספציפיות לכל רשומה (כמו עבודה או אישי) כדי להציע אפשרויות בחירה מפורטות יותר בממשק של האפליקציה. - שמירת הנתונים באופן מיידי: ה-URI של הסשן מעניק הרשאת קריאה זמנית. אם תצטרכו לגשת לפרטי הקשר האלה בהמשך (אחרי שתהליך האפליקציה יופסק), האפליקציה שלכם תצטרך לשמור את נתוני הקשר.
- אל תסתמכו על נתוני החשבון: כדי להגן על פרטיות המשתמשים ולמנוע טכניקות של זיהוי ייחודי (fingerprinting), המערכת מסירה מהתוצאות מטא-נתונים שספציפיים לחשבון.