أداة اختيار جهات الاتصال

"منتقي جهات الاتصال في Android" هو واجهة موحّدة وقابلة للتصفّح تتيح للمستخدمين مشاركة جهات الاتصال مع تطبيقك. يتوفّر هذا المنتقي على الأجهزة التي تعمل بالإصدار 17 من نظام التشغيل Android (مستوى واجهة برمجة التطبيقات 37) أو الإصدارات الأحدث، ويقدّم بديلاً يحافظ على الخصوصية لإذن READ_CONTACTS الواسع النطاق. بدلاً من طلب الوصول إلى دفتر عناوين المستخدم بالكامل، يحدّد تطبيقك حقول البيانات التي يحتاجها، مثل أرقام الهواتف أو عناوين البريد الإلكتروني، ويختار المستخدم جهات اتصال معيّنة لمشاركتها. يمنح هذا الإذن تطبيقك إمكانية الوصول للقراءة إلى البيانات المحدّدة فقط، ما يضمن التحكّم الدقيق مع توفير تجربة مستخدم متّسقة من خلال إمكانات البحث المدمجة والتبديل بين الملفات الشخصية والاختيار المتعدّد بدون الحاجة إلى إنشاء واجهة المستخدم أو صيانتها.

دمج "منتقي جهات الاتصال"

لدمج "منتقي جهات الاتصال"، استخدِم الغرض ContactsPickerSessionContract.ACTION_PICK_CONTACTS. يؤدي هذا الغرض إلى تشغيل المنتقي وإرجاع جهات الاتصال المحدّدة إلى تطبيقك.

وبخلاف ACTION_PICK القديم، يتيح لك "منتقي جهات الاتصال" تحديد حقول بيانات متعدّدة يتطلّبها تطبيقك في الوقت نفسه. يمكنك إجراء ذلك باستخدام ContactsPickerSessionContract.EXTRA_PICK_CONTACTS_REQUESTED_DATA_FIELDS، وتمرير ArrayList<String> من أنواع MIME المحدّدة في ContactsContract.CommonDataKinds.

تشمل أنواع MIME الشائعة ما يلي:

  • ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE
  • ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE
  • ContactsContract.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)
        }
    }
}

وضع الاختيار

يتم تعديل واجهة مستخدم "منتقي جهات الاتصال" وفقًا لحقول البيانات المطلوبة. بناءً على هذه المتطلبات، يمكن للمستخدمين اختيار سجل جهة اتصال كامل عند الحاجة إلى حقول متعددة، أو اختيار عناصر بيانات محددة من ضمن معلومات جهة الاتصال.

أوضاع واجهة المستخدم المختلفة في &quot;أداة اختيار جهات الاتصال&quot;
الشكل 1: تتكيّف واجهة "منتقي جهات الاتصال" مع حقول البيانات المطلوبة (اختيار جهة اتصال واحدة أو جهات اتصال متعدّدة أو أرقام هواتف متعدّدة).

اختيار جهة اتصال واحدة

في هذا المثال، يطلب التطبيق أرقام الهواتف فقط. ستعمل أداة الاختيار على فلترة القائمة لعرض جهات الاتصال التي تتضمّن أرقام هواتف فقط، وتتيح للمستخدم اختيار رقم معيّن.

// 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)

اختيار جهات اتصال متعددة

لتفعيل ميزة الاختيار المتعدّد، أضِف 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()
}

التوافق مع الأنظمة القديمة

بالنسبة إلى التطبيقات التي تستهدف الإصدار 17 من نظام التشغيل Android (مستوى واجهة برمجة التطبيقات 37) والإصدارات الأحدث، يرقّي النظام تلقائيًا الغرض الحالي Intent.ACTION_PICK لاستخدام واجهة جديدة لـ "منتقي جهات الاتصال".

إذا كان تطبيقك يستخدم حاليًا ACTION_PICK، لن تحتاج إلى تغيير الرمز البرمجي لتلقّي واجهة المستخدم الجديدة. ومع ذلك، للاستفادة من الميزات الجديدة، مثل تلقّي Uri واحد لطلب بيانات جهات الاتصال، والتبديل بين الملفات الشخصية وملفات العمل أو طلبات حقول البيانات المتعدّدة، يجب تعديل عملية التنفيذ لاستخدام ContactsPickerSessionContract.ACTION_PICK_CONTACTS أو الإضافات الجديدة للغرض.

الاختبار على حِزم SDK قديمة مستهدَفة

يمكنك اختبار سلوك أداة الاختيار الجديدة على الأجهزة التي تعمل بنظام التشغيل Android 17 والإصدارات الأحدث، حتى إذا كان تطبيقك يستهدف إصدارًا أقدم من حزمة تطوير البرامج (SDK)، وذلك من خلال إضافة القيمة المنطقية الإضافية EXTRA_USE_SYSTEM_CONTACTS_PICKER إلى نية ACTION_PICK.

أفضل الممارسات

  • طلب الأذونات اللازمة فقط: إذا كان تطبيقك يحتاج فقط إلى إرسال رسالة قصيرة، اطلب الإذن Phone.CONTENT_ITEM_TYPE. ستعمل أداة الاختيار تلقائيًا على فلترة جهات الاتصال التي لا تتضمّن أرقام هواتف، ما يؤدي إلى توفير واجهة مستخدم أكثر تنظيمًا للمستخدم.
  • إدارة إدخالات بيانات متعددة لكل جهة اتصال: غالبًا ما تحتوي جهات الاتصال الفردية على عناوين بريد إلكتروني أو أرقام هواتف مختلفة. للمساعدة في ضمان عرضها بوضوح وبشكل بديهي للمستخدم، ننصحك بتجميعها باستخدام ContactsContract.Contacts.LOOKUP_KEY. بالإضافة إلى ذلك، يمكنك استرداد تصنيفات محدّدة لكل إدخال (مثل العمل أو الشخصي) لتقديم خيارات اختيار أكثر دقة ضمن واجهة تطبيقك.
  • حفظ البيانات على الفور: يمنح عنوان URI الخاص بالجلسة إذنًا مؤقتًا بالقراءة. إذا كنت بحاجة إلى الوصول إلى معلومات الاتصال هذه لاحقًا (بعد إنهاء عملية التطبيق)، يجب أن يحتفظ تطبيقك ببيانات جهات الاتصال.
  • عدم الاعتماد على بيانات الحساب: لحماية خصوصية المستخدم ومنع بصمات الإصبع، تتم إزالة البيانات الوصفية الخاصة بالحساب من النتائج.