استرداد قائمة جهات الاتصال

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

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

ملاحظة: تستخدم جميع الأمثلة الواردة في هذا الدرس CursorLoader لاسترداد البيانات من "موفّر جهات الاتصال". يُجري CursorLoader طلب البحث في سلسلة محادثات منفصلة عن سلسلة واجهة المستخدم. يضمن ذلك ألا يؤدي طلب البحث إلى إبطاء أوقات استجابة واجهة المستخدم والتسبب في تجربة سيئة للمستخدم. لمزيد من المعلومات، يُرجى الاطّلاع على الصف التدريبي لنظام التشغيل Android حول تحميل البيانات في الخلفية.

طلب الإذن بقراءة بيانات مقدّم الخدمة

لإجراء أي نوع من عمليات البحث في مقدِّم جهات الاتصال، يجب أن يحصل تطبيقك على إذن READ_CONTACTS. لطلب ذلك، أضِف العنصر <uses-permission> إلى ملف البيان كعنصر فرعي من <manifest>:

    <uses-permission android:name="android.permission.READ_CONTACTS" />

مطابقة جهة اتصال حسب الاسم وإدراج النتائج

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

تحديد ListView وتخطيطات العناصر

لعرض نتائج البحث في ListView، تحتاج إلى ملف تنسيق رئيسي يحدّد واجهة المستخدم بأكملها بما في ذلك ListView، وملف تنسيق عنصر يحدّد سطرًا واحدًا من ListView. على سبيل المثال، يمكنك إنشاء ملف التنسيق الرئيسي res/layout/contacts_list_view.xml باستخدام ملف XML التالي:

<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
          android:id="@android:id/list"
          android:layout_width="match_parent"
          android:layout_height="match_parent"/>

يستخدم ملف XML هذا أداة ListView المدمجة في Android android:id/list.

حدِّد ملف تنسيق العنصر contacts_list_item.xml باستخدام ملف XML التالي:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
          android:id="@android:id/text1"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:clickable="true"/>

يستخدم ملف XML هذا تطبيق Android TextView المصغّر المضمّن android:text1.

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

يحدّد ملفّا التنسيق اللذان كتبتهما واجهة مستخدم تعرض ListView. الخطوة التالية هي كتابة رمز برمجي يستخدم واجهة المستخدم هذه لعرض قائمة بجهات الاتصال.

تحديد جزء يعرض قائمة جهات الاتصال

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

للتعرّف على كيفية استخدام عنصر واحد أو أكثر من عناصر Fragment من Activity، يمكنك قراءة فئة التدريب إنشاء واجهة مستخدم ديناميكية باستخدام "التقسيم".

لمساعدتك في كتابة طلبات بحث في مقدِّم جهات الاتصال، يوفّر إطار عمل Android صف مرافق يُسمى ContactsContract، وتحدّد هذه الفئة ثوابت وأساليب مفيدة للوصول إلى موفّر البيانات. عند استخدام هذه الفئة، لن تحتاج إلى تحديد الثوابت الخاصة بك لمعرّفات الموارد المنتظمة للمحتوى أو أسماء الجداول أو الأعمدة. لاستخدام هذه الفئة، أدرِج العبارة التالية:

Kotlin

import android.provider.ContactsContract

Java

import android.provider.ContactsContract;

بما أنّ الرمز يستخدم CursorLoader لاسترداد البيانات من الموفّر، عليك تحديد أنّه ينفّذ واجهة أداة التحميل LoaderManager.LoaderCallbacks. بالإضافة إلى ذلك، للمساعدة في رصد جهة الاتصال التي يختارها المستخدم من قائمة نتائج البحث، عليك تنفيذ واجهة الأداة AdapterView.OnItemClickListener. على سبيل المثال:

Kotlin

...
import android.support.v4.app.Fragment
import android.support.v4.app.LoaderManager
import android.widget.AdapterView
...
class ContactsFragment :
        Fragment(),
        LoaderManager.LoaderCallbacks<Cursor>,
        AdapterView.OnItemClickListener {

Java

...
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.widget.AdapterView;
...
public class ContactsFragment extends Fragment implements
        LoaderManager.LoaderCallbacks<Cursor>,
        AdapterView.OnItemClickListener {

تحديد المتغيّرات العمومية

حدِّد المتغيّرات العامة المستخدَمة في أجزاء أخرى من الرمز:

Kotlin

...
/*
 * Defines an array that contains column names to move from
 * the Cursor to the ListView.
 */
@SuppressLint("InlinedApi")
private val FROM_COLUMNS: Array<String> = arrayOf(
        if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)) {
            ContactsContract.Contacts.DISPLAY_NAME_PRIMARY
        } else {
            ContactsContract.Contacts.DISPLAY_NAME
        }
)
/*
 * Defines an array that contains resource ids for the layout views
 * that get the Cursor column contents. The id is pre-defined in
 * the Android framework, so it is prefaced with "android.R.id"
 */
private val TO_IDS: IntArray = intArrayOf(android.R.id.text1)
...
class ContactsFragment :
        Fragment(),
        LoaderManager.LoaderCallbacks<Cursor>,
        AdapterView.OnItemClickListener {
    ...
    // Define global mutable variables
    // Define a ListView object
    lateinit var contactsList: ListView
    // Define variables for the contact the user selects
    // The contact's _ID value
    var contactId: Long = 0
    // The contact's LOOKUP_KEY
    var contactKey: String? = null
    // A content URI for the selected contact
    var contactUri: Uri? = null
    // An adapter that binds the result Cursor to the ListView
    private val cursorAdapter: SimpleCursorAdapter? = null

Java

    ...
    /*
     * Defines an array that contains column names to move from
     * the Cursor to the ListView.
     */
    @SuppressLint("InlinedApi")
    private final static String[] FROM_COLUMNS = {
            Build.VERSION.SDK_INT
                    >= Build.VERSION_CODES.HONEYCOMB ?
                    ContactsContract.Contacts.DISPLAY_NAME_PRIMARY :
                    ContactsContract.Contacts.DISPLAY_NAME
    };
    /*
     * Defines an array that contains resource ids for the layout views
     * that get the Cursor column contents. The id is pre-defined in
     * the Android framework, so it is prefaced with "android.R.id"
     */
    private final static int[] TO_IDS = {
           android.R.id.text1
    };
    // Define global mutable variables
    // Define a ListView object
    ListView contactsList;
    // Define variables for the contact the user selects
    // The contact's _ID value
    long contactId;
    // The contact's LOOKUP_KEY
    String contactKey;
    // A content URI for the selected contact
    Uri contactUri;
    // An adapter that binds the result Cursor to the ListView
    private SimpleCursorAdapter cursorAdapter;
    ...

ملاحظة: بما أنّ Contacts.DISPLAY_NAME_PRIMARY يتطلّب الإصدار 3.0 من نظام التشغيل Android (المستوى 11 من واجهة برمجة التطبيقات) أو الإصدارات الأحدث، سيؤدي ضبط minSdkVersion لتطبيقك على 10 أو أقل إلى ظهور تحذير من Android Lint في &quot;استوديو Android&quot;. لإيقاف هذا التحذير، أضِف التعليق التوضيحي @SuppressLint("InlinedApi") قبل تعريف FROM_COLUMNS.

إعداد الجزء

ابدأ Fragment. أضِف الدالة الإنشائية العامة الفارغة التي يتطلبها نظام Android، ثم وسِّع واجهة المستخدم الخاصة بالعنصر Fragment في طريقة رد الاتصال onCreateView(). على سبيل المثال:

Kotlin

    // A UI Fragment must inflate its View
    override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
    ): View? {
        // Inflate the fragment layout
        return inflater.inflate(R.layout.contact_list_fragment, container, false)
    }

Java

    // Empty public constructor, required by the system
    public ContactsFragment() {}

    // A UI Fragment must inflate its View
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        // Inflate the fragment layout
        return inflater.inflate(R.layout.contact_list_fragment,
            container, false);
    }

إعداد CursorAdapter لـ ListView

اضبط SimpleCursorAdapter الذي يربط نتائج البحث بـ ListView. للحصول على العنصر ListView الذي يعرض جهات الاتصال، عليك استدعاء Activity.findViewById() باستخدام النشاط الرئيسي للعنصر Fragment. استخدِم Context لنشاط العنصر الرئيسي عند استدعاء setAdapter(). على سبيل المثال:

Kotlin

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        ...
        // Gets the ListView from the View list of the parent activity
        activity?.also {
            contactsList = it.findViewById<ListView>(R.id.contact_list_view)
            // Gets a CursorAdapter
            cursorAdapter = SimpleCursorAdapter(
                    it,
                    R.layout.contact_list_item,
                    null,
                    FROM_COLUMNS, TO_IDS,
                    0
            )
            // Sets the adapter for the ListView
            contactsList.adapter = cursorAdapter
        }
    }

Java

    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        ...
        // Gets the ListView from the View list of the parent activity
        contactsList =
            (ListView) getActivity().findViewById(R.layout.contact_list_view);
        // Gets a CursorAdapter
        cursorAdapter = new SimpleCursorAdapter(
                getActivity(),
                R.layout.contact_list_item,
                null,
                FROM_COLUMNS, TO_IDS,
                0);
        // Sets the adapter for the ListView
        contactsList.setAdapter(cursorAdapter);
    }

ضبط مستمع جهة الاتصال المحدّدة

عند عرض نتائج بحث، من المفترض أنك تريد السماح للمستخدم باختيار جهة اتصال واحدة لمزيد من المعالجة. على سبيل المثال، عندما ينقر المستخدم على جهة اتصال، يمكنك عرض عنوان جهة الاتصال على خريطة. لتوفير هذه الميزة، عليك أولاً تحديد Fragment الحالي كمتتبِّع النقرات من خلال تحديد أنّ الفئة تنفّذ AdapterView.OnItemClickListener، كما هو موضّح في القسم تحديد جزء يعرض قائمة جهات الاتصال.

لمواصلة إعداد أداة معالجة الأحداث، اربطها بـ ListView من خلال استدعاء الطريقة setOnItemClickListener() في onActivityCreated(). على سبيل المثال:

Kotlin

    fun onActivityCreated(savedInstanceState:Bundle) {
        ...
        // Set the item click listener to be the current fragment.
        contactsList.onItemClickListener = this
        ...
    }

Java

    public void onActivityCreated(Bundle savedInstanceState) {
        ...
        // Set the item click listener to be the current fragment.
        contactsList.setOnItemClickListener(this);
        ...
    }

بما أنّك حدّدت أنّ Fragment الحالي هو OnItemClickListener الخاص بـ ListView، عليك الآن تنفيذ الطريقة المطلوبة onItemClick()، والتي تتعامل مع حدث ناتج عن النقر. يتم توضيح ذلك في قسم لاحق.

تحديد إسقاط

حدِّد قيمة ثابتة تحتوي على الأعمدة التي تريد عرضها من طلب البحث. يعرض كل عنصر في ListView الاسم المعروض لجهة الاتصال، والذي يحتوي على الشكل الرئيسي لاسم جهة الاتصال. في الإصدار 3.0 من Android (الإصدار 11 من واجهة برمجة التطبيقات) والإصدارات الأحدث، يكون اسم هذا العمود Contacts.DISPLAY_NAME_PRIMARY، وفي الإصدارات السابقة، يكون اسمه Contacts.DISPLAY_NAME.

يتم استخدام العمود Contacts._ID في عملية الربط SimpleCursorAdapter. يتم استخدام Contacts._ID وLOOKUP_KEY معًا لإنشاء معرّف موارد منتظم للمحتوى لجهة الاتصال التي يختارها المستخدم.

Kotlin

...
@SuppressLint("InlinedApi")
private val PROJECTION: Array<out String> = arrayOf(
        ContactsContract.Contacts._ID,
        ContactsContract.Contacts.LOOKUP_KEY,
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
            ContactsContract.Contacts.DISPLAY_NAME_PRIMARY
        else
            ContactsContract.Contacts.DISPLAY_NAME
)

Java

...
@SuppressLint("InlinedApi")
private static final String[] PROJECTION =
        {
            Contacts._ID,
            Contacts.LOOKUP_KEY,
            Build.VERSION.SDK_INT
                    >= Build.VERSION_CODES.HONEYCOMB ?
                    ContactsContract.Contacts.DISPLAY_NAME_PRIMARY :
                    ContactsContract.Contacts.DISPLAY_NAME

        };

تحديد الثوابت لفهارس أعمدة المؤشر

للحصول على بيانات من عمود فردي في Cursor، تحتاج إلى فهرس العمود داخل Cursor. يمكنك تحديد ثوابت لفهارس أعمدة Cursor، لأنّ الفهارس هي نفسها ترتيب أسماء الأعمدة في العرض. على سبيل المثال:

Kotlin

// The column index for the _ID column
private const val CONTACT_ID_INDEX: Int = 0
// The column index for the CONTACT_KEY column
private const val CONTACT_KEY_INDEX: Int = 1

Java

// The column index for the _ID column
private static final int CONTACT_ID_INDEX = 0;
// The column index for the CONTACT_KEY column
private static final int CONTACT_KEY_INDEX = 1;

تحديد معايير الاختيار

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

بالنسبة إلى التعبير النصي، حدِّد ثابتًا يسرد أعمدة البحث. على الرغم من أنّ هذا التعبير يمكن أن يحتوي على قيم أيضًا، يُفضَّل تمثيل القيم باستخدام العنصر النائب "؟". أثناء الاسترجاع، يتم استبدال العنصر النائب بقيم من مصفوفة. يضمن استخدام "؟" كعنصر نائب إنشاء مواصفات البحث عن طريق الربط بدلاً من تجميع SQL. تمنع هذه الممارسة احتمالية حدوث هجمات SQL Injection الضارة. على سبيل المثال:

Kotlin

// Defines the text expression
@SuppressLint("InlinedApi")
private val SELECTION: String =
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
            "${ContactsContract.Contacts.DISPLAY_NAME_PRIMARY} LIKE ?"
        else
            "${ContactsContract.Contacts.DISPLAY_NAME} LIKE ?"
...
    // Defines a variable for the search string
    private val searchString: String = ...
    // Defines the array to hold values that replace the ?
    private val selectionArgs = arrayOf<String>(searchString)

Java

    // Defines the text expression
    @SuppressLint("InlinedApi")
    private static final String SELECTION =
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ?
            Contacts.DISPLAY_NAME_PRIMARY + " LIKE ?" :
            Contacts.DISPLAY_NAME + " LIKE ?";
    // Defines a variable for the search string
    private String searchString;
    // Defines the array to hold values that replace the ?
    private String[] selectionArgs = { searchString };

تحديد طريقة onItemClick()‎

في قسم سابق، قمت بتعيين متتبِّع النقرات على العنصر لـ ListView. الآن، نفِّذ الإجراء الخاص بمعالج الأحداث من خلال تحديد الطريقة AdapterView.OnItemClickListener.onItemClick():

Kotlin

    override fun onItemClick(parent: AdapterView<*>, view: View?, position: Int, id: Long) {
        // Get the Cursor
        val cursor: Cursor? = (parent.adapter as? CursorAdapter)?.cursor?.apply {
            // Move to the selected contact
            moveToPosition(position)
            // Get the _ID value
            contactId = getLong(CONTACT_ID_INDEX)
            // Get the selected LOOKUP KEY
            contactKey = getString(CONTACT_KEY_INDEX)
            // Create the contact's content Uri
            contactUri = ContactsContract.Contacts.getLookupUri(contactId, mContactKey)
            /*
             * You can use contactUri as the content URI for retrieving
             * the details for a contact.
             */
        }
    }

Java

    @Override
    public void onItemClick(
        AdapterView<?> parent, View item, int position, long rowID) {
        // Get the Cursor
        Cursor cursor = parent.getAdapter().getCursor();
        // Move to the selected contact
        cursor.moveToPosition(position);
        // Get the _ID value
        contactId = cursor.getLong(CONTACT_ID_INDEX);
        // Get the selected LOOKUP KEY
        contactKey = cursor.getString(CONTACT_KEY_INDEX);
        // Create the contact's content Uri
        contactUri = Contacts.getLookupUri(contactId, mContactKey);
        /*
         * You can use contactUri as the content URI for retrieving
         * the details for a contact.
         */
    }

إعداد أداة التحميل

بما أنّك تستخدم CursorLoader لاسترداد البيانات، عليك تهيئة سلسلة الخلفية والمتغيّرات الأخرى التي تتحكّم في الاسترداد غير المتزامن. نفِّذ عملية الإعداد في onCreate() كما هو موضّح في المثال التالي:

Kotlin

class ContactsFragment :
        Fragment(),
        LoaderManager.LoaderCallbacks<Cursor> {
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        // Always call the super method first
        super.onCreate(savedInstanceState)
        ...
        // Initializes the loader
        loaderManager.initLoader(0, null, this)

Java

public class ContactsFragment extends Fragment implements
        LoaderManager.LoaderCallbacks<Cursor> {
    ...
    // Called just before the Fragment displays its UI
    @Override
    public void onCreate(Bundle savedInstanceState) {
        // Always call the super method first
        super.onCreate(savedInstanceState);
        ...
        // Initializes the loader
        getLoaderManager().initLoader(0, null, this);

تنفيذ onCreateLoader()‎

نفِّذ الطريقة onCreateLoader()، التي يتم استدعاؤها من خلال إطار عمل أداة التحميل مباشرةً بعد استدعاء initLoader().

في onCreateLoader()، اضبط نمط سلسلة البحث. لتحويل سلسلة إلى نمط، أدرِج أحرف "%" (علامة النسبة المئوية) لتمثيل تسلسل من صفر أو أكثر من الأحرف، أو أحرف "_" (الشرطة السفلية) لتمثيل حرف واحد، أو كليهما. على سبيل المثال، يتطابق النمط "%Jefferson%" مع كل من "Thomas Jefferson" و "Jefferson Davis".

إرجاع CursorLoader جديد من الطريقة بالنسبة إلى معرّف الموارد المنتظم للمحتوى، استخدِم Contacts.CONTENT_URI. يشير معرّف الموارد المنتظم (URI) هذا إلى الجدول بأكمله، كما هو موضّح في المثال التالي:

Kotlin

    ...
    override fun onCreateLoader(loaderId: Int, args: Bundle?): Loader<Cursor> {
        /*
         * Makes search string into pattern and
         * stores it in the selection array
         */
        selectionArgs[0] = "%$mSearchString%"
        // Starts the query
        return activity?.let {
            return CursorLoader(
                    it,
                    ContactsContract.Contacts.CONTENT_URI,
                    PROJECTION,
                    SELECTION,
                    selectionArgs,
                    null
            )
        } ?: throw IllegalStateException()
    }

Java

    ...
    @Override
    public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
        /*
         * Makes search string into pattern and
         * stores it in the selection array
         */
        selectionArgs[0] = "%" + searchString + "%";
        // Starts the query
        return new CursorLoader(
                getActivity(),
                ContactsContract.Contacts.CONTENT_URI,
                PROJECTION,
                SELECTION,
                selectionArgs,
                null
        );
    }

تنفيذ onLoadFinished() وonLoaderReset()

نفِّذ الطريقة onLoadFinished(). يستدعي إطار عمل أداة التحميل onLoadFinished() عندما يعرض مقدِّم جهات الاتصال نتائج طلب البحث. في هذه الطريقة، ضع النتيجة Cursor في SimpleCursorAdapter. يؤدي ذلك إلى تعديل ListView تلقائيًا بنتائج البحث:

Kotlin

    override fun onLoadFinished(loader: Loader<Cursor>, cursor: Cursor) {
        // Put the result Cursor in the adapter for the ListView
        cursorAdapter?.swapCursor(cursor)
    }

Java

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        // Put the result Cursor in the adapter for the ListView
        cursorAdapter.swapCursor(cursor);
    }

يتم استدعاء الطريقة onLoaderReset() عندما يرصد إطار عمل أداة التحميل أنّ النتيجة Cursor تحتوي على بيانات قديمة. احذف الإشارة إلى SimpleCursorAdapter في Cursor الحالي. في حال عدم تنفيذ ذلك، لن يعيد إطار عمل أداة التحميل تدوير Cursor، ما يؤدي إلى تسرُّب الذاكرة. على سبيل المثال:

Kotlin

    override fun onLoaderReset(loader: Loader<Cursor>) {
        // Delete the reference to the existing Cursor
        cursorAdapter?.swapCursor(null)
    }

Java

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        // Delete the reference to the existing Cursor
        cursorAdapter.swapCursor(null);

    }

لديك الآن الأجزاء الرئيسية من تطبيق يطابق سلسلة بحث مع أسماء جهات الاتصال ويعرض النتيجة في ListView. يمكن للمستخدم النقر على اسم جهة اتصال لاختيارها. يؤدي ذلك إلى تشغيل مستمع، حيث يمكنك العمل بشكل أكبر مع بيانات جهة الاتصال. على سبيل المثال، يمكنك استرداد تفاصيل جهة الاتصال. لمعرفة كيفية إجراء ذلك، انتقِل إلى الدرس التالي، استرداد تفاصيل جهة اتصال.

لمزيد من المعلومات حول واجهات مستخدم البحث، يُرجى قراءة دليل واجهة برمجة التطبيقات إنشاء واجهة بحث.

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

مطابقة جهة اتصال حسب نوع معيّن من البيانات

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

لتنفيذ هذا النوع من الاسترداد، عليك أولاً تنفيذ الرمز التالي، كما هو موضّح في الأقسام السابقة:

  • طلب الإذن بقراءة بيانات مقدّم الخدمة
  • تحديد تخطيطات ListView والعناصر
  • حدِّد Fragment يعرض قائمة جهات الاتصال.
  • تحديد المتغيرات العامة
  • إعداد الجزء
  • إعداد CursorAdapter لـ ListView
  • اضبط مستمع جهة الاتصال المحدّدة.
  • حدِّد ثوابت لفهارس أعمدة Cursor.

    على الرغم من أنّك تسترد البيانات من جدول مختلف، فإنّ ترتيب الأعمدة في العرض هو نفسه، لذا يمكنك استخدام الفهارس نفسها للمؤشر.

  • حدِّد طريقة onItemClick()‎.
  • ابدأ عملية التحميل.
  • نفِّذ الدالتَين onLoadFinished() وonLoaderReset().

توضّح لك الخطوات التالية الرمز الإضافي الذي تحتاج إليه لمطابقة سلسلة بحث مع نوع معيّن من بيانات التفاصيل وعرض النتائج.

اختيار نوع البيانات والجدول

للبحث عن نوع معيّن من بيانات التفاصيل، عليك معرفة قيمة نوع MIME المخصّص لنوع البيانات. لكل نوع بيانات قيمة نوع MIME فريدة يحدّدها الثابت CONTENT_ITEM_TYPE في الفئة الفرعية من ContactsContract.CommonDataKinds المرتبطة بنوع البيانات. تحتوي الفئات الفرعية على أسماء تشير إلى نوع البيانات، على سبيل المثال، الفئة الفرعية لبيانات البريد الإلكتروني هي ContactsContract.CommonDataKinds.Email، ويتم تحديد نوع MIME المخصّص لبيانات البريد الإلكتروني من خلال الثابت Email.CONTENT_ITEM_TYPE.

استخدِم جدول ContactsContract.Data لإجراء البحث. يتم تحديد جميع الثوابت التي تحتاجها للإسقاط وعبارة الاختيار ونظام الترتيب في هذا الجدول أو يتم اكتسابها منه.

تحديد إسقاط

لتحديد إسقاط، اختَر عمودًا واحدًا أو أكثر من الأعمدة المحدّدة في ContactsContract.Data أو الفئات التي تكتسب منها. يُجري مقدِّم جهات الاتصال عملية دمج ضمنية بين ContactsContract.Data والجداول الأخرى قبل عرض الصفوف. على سبيل المثال:

Kotlin

@SuppressLint("InlinedApi")
private val PROJECTION: Array<out String> = arrayOf(
        /*
         * The detail data row ID. To make a ListView work,
         * this column is required.
         */
        ContactsContract.Data._ID,
        // The primary display name
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
            ContactsContract.Data.DISPLAY_NAME_PRIMARY
        else
            ContactsContract.Data.DISPLAY_NAME,
        // The contact's _ID, to construct a content URI
        ContactsContract.Data.CONTACT_ID,
        // The contact's LOOKUP_KEY, to construct a content URI
        ContactsContract.Data.LOOKUP_KEY
)

Java

    @SuppressLint("InlinedApi")
    private static final String[] PROJECTION =
        {
            /*
             * The detail data row ID. To make a ListView work,
             * this column is required.
             */
            ContactsContract.Data._ID,
            // The primary display name
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ?
                    ContactsContract.Data.DISPLAY_NAME_PRIMARY :
                    ContactsContract.Data.DISPLAY_NAME,
            // The contact's _ID, to construct a content URI
            ContactsContract.Data.CONTACT_ID,
            // The contact's LOOKUP_KEY, to construct a content URI
            ContactsContract.Data.LOOKUP_KEY // A permanent link to the contact
        };

تحديد معايير البحث

للبحث عن سلسلة ضمن نوع معيّن من البيانات، أنشئ عبارة اختيار من العناصر التالية:

  • اسم العمود الذي يحتوي على سلسلة البحث يختلف هذا الاسم حسب نوع البيانات، لذا عليك العثور على الفئة الفرعية من ContactsContract.CommonDataKinds التي تتوافق مع نوع البيانات، ثم اختيار اسم العمود من تلك الفئة الفرعية. على سبيل المثال، للبحث عن عناوين البريد الإلكتروني، استخدِم العمود Email.ADDRESS.
  • سلسلة البحث نفسها، ويتم تمثيلها بالحرف "؟" في عبارة الاختيار.
  • اسم العمود الذي يحتوي على قيمة نوع MIME المخصّص هذا الاسم هو دائمًا Data.MIMETYPE.
  • قيمة نوع MIME المخصّص لنوع البيانات As described previously, this is the constant CONTENT_ITEM_TYPE in the ContactsContract.CommonDataKinds subclass. على سبيل المثال، قيمة نوع MIME لبيانات البريد الإلكتروني هي Email.CONTENT_ITEM_TYPE. أدرِج القيمة بين علامتَي اقتباس مفردتَين من خلال دمج حرف "'" (علامة اقتباس مفردة) في بداية الثابت ونهايته، وإلا سيفسّر مقدّم الخدمة القيمة على أنّها اسم متغيّر وليس قيمة سلسلة. لست بحاجة إلى استخدام عنصر نائب لهذه القيمة، لأنّك تستخدم قيمة ثابتة بدلاً من قيمة يقدّمها المستخدم.

على سبيل المثال:

Kotlin

/*
 * Constructs search criteria from the search string
 * and email MIME type
 */
private val SELECTION: String =
        /*
         * Searches for an email address
         * that matches the search string
         */
        "${Email.ADDRESS} LIKE ? AND " +
        /*
         * Searches for a MIME type that matches
         * the value of the constant
         * Email.CONTENT_ITEM_TYPE. Note the
         * single quotes surrounding Email.CONTENT_ITEM_TYPE.
         */
        "${ContactsContract.Data.MIMETYPE } = '${Email.CONTENT_ITEM_TYPE}'"

Java

    /*
     * Constructs search criteria from the search string
     * and email MIME type
     */
    private static final String SELECTION =
            /*
             * Searches for an email address
             * that matches the search string
             */
            Email.ADDRESS + " LIKE ? " + "AND " +
            /*
             * Searches for a MIME type that matches
             * the value of the constant
             * Email.CONTENT_ITEM_TYPE. Note the
             * single quotes surrounding Email.CONTENT_ITEM_TYPE.
             */
            ContactsContract.Data.MIMETYPE + " = '" + Email.CONTENT_ITEM_TYPE + "'";

بعد ذلك، حدِّد المتغيّرات التي ستحتوي على وسيطة الاختيار:

Kotlin

    private var searchString: String? = null
    private val selectionArgs: Array<String> = arrayOf("")

Java

    String searchString;
    String[] selectionArgs = { "" };

تنفيذ onCreateLoader()

بعد تحديد البيانات التي تريدها وكيفية العثور عليها، حدِّد طلب بحث في عملية تنفيذ onCreateLoader(). يمكنك عرض CursorLoader جديد من هذه الطريقة، باستخدام الإسقاط وتعبير نص التحديد ومصفوفة التحديد كوسيطات. بالنسبة إلى معرّف الموارد المنتظم للمحتوى، استخدِم Data.CONTENT_URI. على سبيل المثال:

Kotlin

    override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
        // OPTIONAL: Makes search string into pattern
        searchString = "%$mSearchString%"

        searchString?.also {
            // Puts the search string into the selection criteria
            selectionArgs[0] = it
        }
        // Starts the query
        return activity?.let {
            CursorLoader(
                    it,
                    ContactsContract.Data.CONTENT_URI,
                    PROJECTION,
                    SELECTION,
                    selectionArgs,
                    null
            )
        } ?: throw IllegalStateException()
    }

Java

@Override
    public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
        // OPTIONAL: Makes search string into pattern
        searchString = "%" + searchString + "%";
        // Puts the search string into the selection criteria
        selectionArgs[0] = searchString;
        // Starts the query
        return new CursorLoader(
                getActivity(),
                Data.CONTENT_URI,
                PROJECTION,
                SELECTION,
                selectionArgs,
                null
        );
    }

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

مطابقة جهة اتصال حسب أي نوع من البيانات

يؤدي استرداد جهة اتصال استنادًا إلى أي نوع من البيانات إلى عرض جهات الاتصال إذا تطابقت أي من بياناتها مع سلسلة البحث، بما في ذلك الاسم وعنوان البريد الإلكتروني والعنوان البريدي ورقم الهاتف وما إلى ذلك. يؤدي ذلك إلى مجموعة واسعة من نتائج البحث. على سبيل المثال، إذا كانت سلسلة البحث هي "Doe"، فإنّ البحث عن أي نوع بيانات يعرض جهة الاتصال "John Doe"، كما يعرض جهات الاتصال التي تعيش في "Doe Street".

لتنفيذ هذا النوع من الاسترداد، عليك أولاً تنفيذ الرمز التالي، كما هو موضّح في الأقسام السابقة:

  • طلب الإذن بقراءة بيانات مقدّم الخدمة
  • تحديد تخطيطات ListView والعناصر
  • حدِّد Fragment يعرض قائمة جهات الاتصال.
  • تحديد المتغيرات العامة
  • إعداد الجزء
  • إعداد CursorAdapter لـ ListView
  • ضبط مستمع جهة الاتصال المحدّدة
  • حدِّد توقعات.
  • تحديد الثوابت لفهارس أعمدة المؤشر

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

  • حدِّد طريقة onItemClick().
  • ابدأ عملية التحميل.
  • نفِّذ onLoadFinished()‎ وonLoaderReset()‎.

توضّح لك الخطوات التالية الرمز الإضافي الذي تحتاج إليه لمطابقة سلسلة بحث مع أي نوع من البيانات وعرض النتائج.

إزالة معايير الاختيار

لا تحدِّد ثوابت SELECTION أو متغيّر mSelectionArgs. لا يتم استخدام هذه المعلمات في هذا النوع من الاسترداد.

تنفيذ onCreateLoader()

نفِّذ الطريقة onCreateLoader() ، مع عرض CursorLoader جديد. لست بحاجة إلى تحويل سلسلة البحث إلى نمط، لأنّ &quot;مقدِّم جهات الاتصال&quot; يفعل ذلك تلقائيًا. استخدِم Contacts.CONTENT_FILTER_URI كعنوان URI الأساسي، وألحِق به سلسلة البحث من خلال استدعاء Uri.withAppendedPath(). يؤدي استخدام معرّف الموارد الموحّد هذا إلى بدء البحث تلقائيًا عن أي نوع من البيانات، كما هو موضّح في المثال التالي:

Kotlin

    override fun onCreateLoader(loaderId: Int, args: Bundle?): Loader<Cursor> {
        /*
         * Appends the search string to the base URI. Always
         * encode search strings to ensure they're in proper
         * format.
         */
        val contentUri: Uri = Uri.withAppendedPath(
                ContactsContract.Contacts.CONTENT_FILTER_URI,
                Uri.encode(searchString)
        )
        // Starts the query
        return activity?.let {
            CursorLoader(
                    it,
                    contentUri,
                    PROJECTION2,
                    null,
                    null,
                    null
            )
        } ?: throw IllegalStateException()
    }

Java

    @Override
    public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
        /*
         * Appends the search string to the base URI. Always
         * encode search strings to ensure they're in proper
         * format.
         */
        Uri contentUri = Uri.withAppendedPath(
                Contacts.CONTENT_FILTER_URI,
                Uri.encode(searchString));
        // Starts the query
        return new CursorLoader(
                getActivity(),
                contentUri,
                PROJECTION,
                null,
                null,
                null
        );
    }

تشكّل مقتطفات الرموز البرمجية هذه أساس تطبيق يُجري بحثًا واسع النطاق في مقدِّم جهات الاتصال. تكون هذه الطريقة مفيدة للتطبيقات التي تريد تنفيذ وظائف مشابهة لشاشة قائمة جهات الاتصال في تطبيق "جهات الاتصال".