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