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

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

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

ملاحظة: تستخدم جميع الأمثلة في هذا الدرس 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 هذا تطبيق Android ListView المصغّر 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، يمكنك الاطّلاع على صف التدريب إنشاء واجهة مستخدم ديناميكية باستخدام Fragments.

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

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 يتطلب Android 3.0 (الإصدار 11 من واجهة برمجة التطبيقات) أو إصدارًا أحدث، سيؤدي ضبط minSdkVersion في تطبيقك على 10 أو أقل إلى إنشاء تحذير Android Lint في "استوديو Android". لإيقاف هذا التحذير، أضِف التعليق التوضيحي @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 الاسم المعروض لجهة الاتصال، والذي يحتوي على الشكل الرئيسي لاسم جهة الاتصال. في نظام Android 3.0 (الإصدار 11 من واجهة برمجة التطبيقات) والإصدارات الأحدث، اسم هذا العمود هو Contacts.DISPLAY_NAME_PRIMARY، في الإصدارات السابقة له، يكون اسمه Contacts.DISPLAY_NAME.

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

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 (لغة الاستعلام البنيوية) ضارة. مثلاً:

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%" مع كل من "توماس جيفرسون" و "جيفرسون ديفيس".

عرض 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. يمكن للمستخدم النقر على اسم جهة اتصال لتحديده. يؤدي هذا إلى تشغيل مستمع، حيث يمكنك العمل بشكلٍ أكبر على بيانات جهة الاتصال. على سبيل المثال، يمكنك استرداد تفاصيل جهة الاتصال. لمعرفة كيفية إجراء ذلك، واصِل مشاهدة الدرس التالي، استرداد التفاصيل لجهة اتصال.

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

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

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

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

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

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

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

  • قم بتحديد طريقة 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 المخصصة لنوع البيانات. وكما هو موضّح سابقًا، تمثّل هذه السمة CONTENT_ITEM_TYPE الثابت في الفئة الفرعية ContactsContract.CommonDataKinds. على سبيل المثال، قيمة نوع 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 والعناصر
  • حدد جزءاً يعرض قائمة جهات الاتصال.
  • تعريف المتغيرات العمومية.
  • قم بتهيئة الجزء.
  • ابدأ إعداد CursorAdapter لـ ListView.
  • ضبط مستمع جهات الاتصال المحدَّد
  • تحديد التوقع.
  • تحديد الثوابت لمؤشرات عمود المؤشر.

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

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

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

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

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

تنفيذ onCreateLoader()

نفِّذ طريقة onCreateLoader() مع عرض عنصر CursorLoader جديد. لن تحتاج إلى تحويل سلسلة البحث إلى نمط، لأن "مقدِّم جهات الاتصال" يقوم بذلك تلقائيًا. استخدِم 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
        );
    }

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