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

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

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

ملاحظة: تستخدِم جميع الأمثلة في هذا الدرس 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 هذا تطبيق TextView المصغّر المتوافق مع Android android:text1.

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

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

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

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

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

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

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 في "استوديو 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 الاسم المعروض لجهة الاتصال، والذي يحتوي على النموذج الرئيسي لاسم جهة الاتصال. في الإصدار 3.0 من Android (الإصدار 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 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) لمحتوى الصورة. يشير معرّف 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 والعناصر.
  • حدِّد مقتطفًا يعرض قائمة جهات الاتصال.
  • حدِّد المتغيّرات العامة.
  • اضبط الإعدادات الأولية للجزء.
  • إعداد 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".

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

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

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

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

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

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

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

تنفيذ onCreateLoader()

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

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
        );
    }

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