"منتقي جهات الاتصال في Android" هو واجهة موحّدة قابلة للتصفّح تتيح للمستخدمين مشاركة جهات الاتصال مع تطبيقك. يتوفّر هذا المنتقي على الأجهزة التي تعمل بالإصدار 17 من نظام التشغيل Android (مستوى واجهة برمجة التطبيقات 37) أو الإصدارات الأحدث، ويقدّم بديلاً يحافظ على الخصوصية للإذن الواسع النطاق READ_CONTACTS. بدلاً من طلب الوصول إلى دفتر العناوين بالكامل، يحدّد تطبيقك حقول البيانات التي يحتاجها، مثل أرقام الهواتف أو عناوين البريد الإلكتروني، ويختار المستخدم جهات اتصال محدّدة لمشاركتها. يمنح ذلك تطبيقك إذن القراءة للبيانات المحدّدة فقط، ما يضمن التحكّم الدقيق مع توفير تجربة مستخدم متّسقة تتضمّن إمكانات البحث المضمّنة والتبديل بين الملفات الشخصية والاختيار المتعدّد بدون الحاجة إلى إنشاء واجهة المستخدِم أو صيانتها.
دمج "منتقي جهات الاتصال"
لدمج "منتقي جهات الاتصال"، استخدِم الغرض Intent.ACTION_PICK_CONTACTS.
يؤدي هذا الغرض إلى تشغيل المنتقي وعرض "جهات الاتصال" المحدّدة في تطبيقك.
بخلاف 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) للجلسة. يمنح معرّف الموارد المنتظم هذا إذن قراءة مؤقتًا للبيانات المحدّدة.
يمكنك طلب معرّف الموارد المنتظم هذا باستخدام 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()
}
التوافق مع الأنظمة القديمة
بالنسبة إلى التطبيقات التي تستهدف الإصدار 17 من نظام التشغيل Android (مستوى واجهة برمجة التطبيقات 37) والإصدارات الأحدث، يرقّي النظام تلقائيًا الغرض الحالي Intent.ACTION_PICK لاستخدام واجهة جديدة لـ"منتقي جهات الاتصال".
إذا كان تطبيقك يستخدم ACTION_PICK، لن تحتاج إلى تغيير الرمز البرمجي لتلقّي واجهة المستخدِم الجديدة. ومع ذلك، للاستفادة من الميزات الجديدة، مثل
تلقّي `معرّف موارد منتظم (URI)` واحد لطلب بيانات جهات الاتصال أو التبديل بين الملفات الشخصية و &
ملفات العمل أو طلبات حقول البيانات المتعدّدة، يجب تعديل عملية
التنفيذ لاستخدام `Intent.ACTION_PICK_CONTACTS` أو إضافات الأغراض الجديدة.UriIntent.ACTION_PICK_CONTACTS
الاختبار على حِزم تطوير البرامج (SDK) القديمة المستهدَفة
يمكنك اختبار سلوك المنتقي الجديد على الأجهزة التي تعمل بالإصدار 17 من نظام التشغيل Android والإصدارات الأحدث حتى إذا كان تطبيقك يستهدف إصدارًا أقل من حزمة تطوير البرامج (SDK)، وذلك من خلال إضافة الإضافة المنطقية EXTRA_USE_SYSTEM_CONTACTS_PICKER إلى غرض ACTION_PICK.
أفضل الممارسات
- طلب ما تحتاج إليه فقط: إذا كان تطبيقك يحتاج فقط إلى إرسال رسالة قصيرة SMS،
اطلب
Phone.CONTENT_ITEM_TYPE. سيُفلتر المنتقي تلقائيًا جهات الاتصال التي ليس لديها أرقام هواتف، ما يؤدي إلى ظهور واجهة مستخدِم أكثر وضوحًا للمستخدم. - إدارة إدخالات البيانات المتعدّدة لكل جهة اتصال: غالبًا ما تحتوي جهات الاتصال الفردية على
عناوين بريد إلكتروني أو أرقام هواتف مختلفة. للمساعدة في ضمان عرض هذه البيانات بوضوح وبشكل بديهي للمستخدم، يُنصح بتجميعها باستخدام
ContactsContract.Contacts.LOOKUP_KEY. بالإضافة إلى ذلك، يمكنك استرداد تصنيفات محدّدة لكل إدخال (مثل العمل أو شخصي) لتقديم خيارات اختيار أكثر دقة ضمن واجهة تطبيقك. - الاحتفاظ بالبيانات على الفور: يمنح معرّف الموارد المنتظم للجلسة إذن قراءة مؤقتًا. إذا كنت بحاجة إلى الوصول إلى معلومات جهة الاتصال هذه لاحقًا (بعد إيقاف عملية تطبيقك)، يجب أن يحتفظ تطبيقك ببيانات جهة الاتصال.
- عدم الاعتماد على بيانات الحساب: لحماية خصوصية المستخدم ومنع تحديد بصمة الجهاز، تتم إزالة البيانات الوصفية الخاصة بالحساب من النتائج.