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

أداة اختيار جهات الاتصال في 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_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)
        }
    }
}

وضع الاختيار

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

أوضاع واجهة المستخدم المختلفة لأداة اختيار جهات الاتصال
الشكل 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 {
    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 أو إضافات Intent الجديدة.

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

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

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

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