‫Android Kotlin Fundamentals 08.3 فلترة البيانات وعرض التفاصيل باستخدام بيانات الإنترنت

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

مقدمة

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

ما يجب معرفته

  • كيفية إنشاء واستخدام الأجزاء
  • كيفية التنقّل بين الأجزاء واستخدام Safe Args (إضافة Gradle) لتمرير البيانات بين الأجزاء
  • كيفية استخدام مكوّنات البنية، بما في ذلك نماذج العرض، ومصانع نماذج العرض، وعمليات التحويل، وLiveData
  • كيفية استرداد البيانات المرمّزة بتنسيق JSON من خدمة ويب REST وتحليل هذه البيانات إلى عناصر Kotlin باستخدام مكتبتَي Retrofit وMoshi

ما ستتعرّف عليه

  • كيفية استخدام تعبيرات الربط المعقّدة في ملفات التصميم
  • كيفية إرسال طلبات Retrofit إلى خدمة ويب تتضمّن خيارات طلب بحث

المهام التي ستنفذها

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

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

في هذا الإصدار من التطبيق، يمكنك العمل مع نوع العقار (إيجار مقابل شراء) وإضافة رمز إلى تصميم الشبكة لوضع علامة على العقارات المعروضة للبيع:

يمكنك تعديل قائمة خيارات التطبيق لفلترة الشبكة وعرض العقارات المتاحة للاستئجار أو البيع فقط:

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

حتى الآن، كان الجزء الوحيد من بيانات موقع Mars الذي استخدمته هو عنوان URL لصورة الموقع. لكنّ بيانات المكان المخصّص للاستئجار، والتي حدّدتها في الفئة MarsProperty، تتضمّن أيضًا معرّفًا وسعرًا ونوعًا (مكان مخصّص للاستئجار أو للبيع). لتذكيرك، إليك مقتطف من بيانات JSON التي تحصل عليها من خدمة الويب:

{
   "price":8000000,
   "id":"424908",
   "type":"rent",
   "img_src": "http://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631290305226E03_DXXX.jpg"
},

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

الخطوة 1: تعديل MarsProperty لتضمين النوع

تحدّد الفئة MarsProperty بنية البيانات لكل سمة تقدّمها خدمة الويب. في درس برمجة سابق، استخدمت مكتبة Moshi لتحليل استجابة JSON الأولية من خدمة الويب الخاصة بكوكب المريخ إلى عناصر بيانات MarsProperty فردية.

في هذه الخطوة، ستضيف بعض المنطق إلى الفئة MarsProperty للإشارة إلى ما إذا كانت هناك عقارات متاحة للاستئجار أم لا (أي ما إذا كان النوع هو السلسلة "rent" أو "buy"). ستستخدم هذا المنطق في أكثر من مكان، لذا من الأفضل إضافته هنا في فئة البيانات بدلاً من تكراره.

  1. افتح تطبيق MarsRealEstate من آخر تجربة عملية. (يمكنك تنزيل تطبيق MarsRealEstateGrid إذا لم يكن مثبّتًا لديك).
  2. فتح "network/MarsProperty.kt" أضِف نصًا برمجيًا إلى تعريف الفئة MarsProperty، وأضِف دالة جلب مخصّصة للسمة isRental تعرض القيمة true إذا كان العنصر من النوع "rent".
data class MarsProperty(
       val id: String,
       @Json(name = "img_src") val imgSrcUrl: String,
       val type: String,
       val price: Double)  {
   val isRental
       get() = type == "rent"
}

الخطوة 2: تعديل تخطيط عنصر الشبكة

الآن، عدِّل تخطيط السلعة لشبكة الصور لعرض عنصر قابل للرسم بعلامة الدولار فقط على صور الأماكن المتاحة للإيجار:

باستخدام تعبيرات ربط البيانات، يمكنك إجراء هذا الاختبار بالكامل في تنسيق XML لعناصر الشبكة.

  1. فتح "res/layout/grid_view_item.xml" هذا هو ملف التصميم لكل خلية فردية في تصميم الشبكة الخاص بـ RecyclerView. يحتوي الملف حاليًا على العنصر <ImageView> فقط لصورة المكان المخصّص للاستئجار.
  2. داخل العنصر <data>، أضِف العنصر <import> للفئة View. يمكنك استخدام عمليات الاستيراد عندما تريد استخدام مكوّنات فئة داخل تعبير ربط البيانات في ملف تنسيق. في هذه الحالة، ستستخدم الثوابت View.GONE وView.VISIBLE، لذا عليك الوصول إلى الفئة View.
<import type="android.view.View"/>
  1. أحِط عرض الصورة بالكامل بعلامة FrameLayout، للسماح بتجميع الرسم القابل للرسم بعلامة الدولار فوق صورة المكان المخصّص للاستئجار.
<FrameLayout
   android:layout_width="match_parent"
   android:layout_height="170dp">
             <ImageView 
                    android:id="@+id/mars_image"
            ...
</FrameLayout>
  1. بالنسبة إلى ImageView، غيِّر السمة android:layout_height إلى match_parent لملء العنصر الرئيسي الجديد FrameLayout.
android:layout_height="match_parent"
  1. أضِف عنصر <ImageView> ثانيًا أسفل العنصر الأول مباشرةً، داخل FrameLayout. استخدِم التعريف الموضّح أدناه. تظهر هذه الصورة في أسفل يسار عنصر الشبكة، فوق صورة المريخ، وتستخدم العنصر القابل للرسم المحدّد في res/drawable/ic_for_sale_outline.xml لرمز علامة الدولار.
<ImageView
   android:id="@+id/mars_property_type"
   android:layout_width="wrap_content"
   android:layout_height="45dp"
   android:layout_gravity="bottom|end"
   android:adjustViewBounds="true"
   android:padding="5dp"
   android:scaleType="fitCenter"
   android:src="@drawable/ic_for_sale_outline"
   tools:src="@drawable/ic_for_sale_outline"/>
  1. أضِف السمة android:visibility إلى طريقة عرض الصورة mars_property_type. استخدِم تعبير ربط لاختبار نوع السمة، وحدِّد إذن الوصول إما إلى View.GONE (للاستئجار) أو View.VISIBLE (للشراء).
 android:visibility="@{property.rental ? View.GONE : View.VISIBLE}"

حتى الآن، لم تظهر لك تعبيرات الربط إلا في التصاميم التي تستخدم متغيرات فردية محدّدة في العنصر <data>. تتسم تعبيرات الربط بالقوة الفائقة وتتيح لك إجراء عمليات مثل الاختبارات والحسابات الرياضية بالكامل داخل تنسيق XML. في هذه الحالة، يمكنك استخدام عامل التشغيل الثلاثي (?:) لإجراء اختبار (هل هذا العنصر مخصّص للاستئجار؟). يمكنك تقديم نتيجة واحدة للقيمة "صحيح" (إخفاء رمز علامة الدولار باستخدام View.GONE) وأخرى للقيمة "خطأ" (إظهار هذا الرمز باستخدام View.VISIBLE).

يظهر ملف grid_view_item.xml الكامل الجديد أدناه:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
       xmlns:app="http://schemas.android.com/apk/res-auto"
       xmlns:tools="http://schemas.android.com/tools">
   <data>
       <import type="android.view.View"/>
       <variable
           name="property"
           type="com.example.android.marsrealestate.network.MarsProperty" />
   </data>
   <FrameLayout
       android:layout_width="match_parent"
       android:layout_height="170dp">

       <ImageView
           android:id="@+id/mars_image"
           android:layout_width="match_parent"
           android:layout_height="match_parent"
           android:scaleType="centerCrop"
           android:adjustViewBounds="true"
           android:padding="2dp"
           app:imageUrl="@{property.imgSrcUrl}"
           tools:src="@tools:sample/backgrounds/scenic"/>

       <ImageView
           android:id="@+id/mars_property_type"
           android:layout_width="wrap_content"
           android:layout_height="45dp"
           android:layout_gravity="bottom|end"
           android:adjustViewBounds="true"
           android:padding="5dp"
           android:scaleType="fitCenter"
           android:src="@drawable/ic_for_sale_outline"
           android:visibility="@{property.rental ? View.GONE : View.VISIBLE}"
           tools:src="@drawable/ic_for_sale_outline"/>
   </FrameLayout>
</layout>
  1. جمِّع التطبيق وشغِّله، ولاحظ أنّ الأماكن غير المخصّصة للاستئجار تتضمّن رمز علامة الدولار.

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

إحدى الطرق التي يمكنك اتّباعها لإنجاز هذه المهمة هي اختبار النوع لكل MarsProperty في شبكة النظرة العامة وعرض السمات المطابقة فقط. ومع ذلك، تحتوي خدمة الويب الفعلية الخاصة بكوكب المريخ على مَعلمة طلب بحث أو خيار (يُسمى filter) يتيح لك الحصول على خصائص من النوع rent أو النوع buy فقط. يمكنك استخدام طلب البحث هذا مع عنوان URL لخدمة الويب realestate في متصفّح على النحو التالي:

https://android-kotlin-fun-mars-server.appspot.com/realestate?filter=buy

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

الخطوة 1: تعديل خدمة Mars API

لتغيير الطلب، عليك إعادة زيارة فئة MarsApiService التي نفّذتها في الدرس العملي الأول ضمن هذه السلسلة. يمكنك تعديل الفئة لتوفير واجهة برمجة تطبيقات للفلترة.

  1. فتح "network/MarsApiService.kt" أسفل عمليات الاستيراد مباشرةً، أنشئ enum باسم MarsApiFilter لتحديد الثوابت التي تتطابق مع قيم طلب البحث التي تتوقّعها خدمة الويب.
enum class MarsApiFilter(val value: String) {
   SHOW_RENT("rent"),
   SHOW_BUY("buy"),
   SHOW_ALL("all") }
  1. عدِّل طريقة getProperties() لتلقّي إدخال سلسلة لطلب الفلتر، وأضِف تعليقًا توضيحيًا إلى هذا الإدخال باستخدام @Query("filter")، كما هو موضّح أدناه.

    استيراد retrofit2.http.Query عندما يُطلب منك ذلك

    تطلب التعليقات التوضيحية @Query من طريقة getProperties() (وبالتالي Retrofit) إرسال طلب خدمة الويب مع خيار الفلتر. في كل مرة يتم فيها استدعاء getProperties()، يتضمّن عنوان URL للطلب الجزء ?filter=type، الذي يوجّه خدمة الويب إلى الرد بنتائج تطابق هذا الطلب.
fun getProperties(@Query("filter") type: String):  

الخطوة 2: تعديل نموذج عرض النظرة العامة

تطلب البيانات من MarsApiService في طريقة getMarsRealEstateProperties() في OverviewViewModel. عليك الآن تعديل هذا الطلب لتضمين وسيطة الفلتر.

  1. فتح "overview/OverviewViewModel.kt" ستظهر لك أخطاء في Android Studio بسبب التغييرات التي أجريتها في الخطوة السابقة. أضِف MarsApiFilter (تعداد قيم الفلتر المحتملة) كمعلَمة إلى طلب getMarsRealEstateProperties().

    انقر على "استيراد" com.example.android.marsrealestate.network.MarsApiFilter عندما يُطلب منك ذلك.
private fun getMarsRealEstateProperties(filter: MarsApiFilter) {
  1. عدِّل طلب getProperties() في خدمة Retrofit لتمرير طلب البحث الخاص بهذا الفلتر كسلسلة.
var getPropertiesDeferred = MarsApi.retrofitService.getProperties(filter.value)
  1. في كتلة init {}، مرِّر MarsApiFilter.SHOW_ALL كوسيط إلى getMarsRealEstateProperties()، لعرض جميع الخصائص عند تحميل التطبيق لأول مرة.
init {
   getMarsRealEstateProperties(MarsApiFilter.SHOW_ALL)
}
  1. في نهاية الفئة، أضِف طريقة updateFilter() تأخذ وسيطة MarsApiFilter وتستدعي getMarsRealEstateProperties() باستخدام هذه الوسيطة.
fun updateFilter(filter: MarsApiFilter) {
   getMarsRealEstateProperties(filter)
}

الخطوة 3: ربط الجزء بقائمة الخيارات

الخطوة الأخيرة هي ربط القائمة الكاملة بالجزء لعرض updateFilter() في نموذج العرض عندما يختار المستخدم أحد خيارات القائمة.

  1. فتح "res/menu/overflow_menu.xml" يحتوي تطبيق MarsRealEstate على قائمة خيارات إضافية حالية توفّر الخيارات الثلاثة المتاحة: عرض جميع الأماكن المتاحة للاستئجار، وعرض الأماكن المتاحة للاستئجار فقط، وعرض الأماكن المتاحة للبيع فقط.
<menu xmlns:android="http://schemas.android.com/apk/res/android">
   <item
       android:id="@+id/show_all_menu"
       android:title="@string/show_all" />
   <item
       android:id="@+id/show_rent_menu"
       android:title="@string/show_rent" />
   <item
       android:id="@+id/show_buy_menu"
       android:title="@string/show_buy" />
</menu>
  1. فتح "overview/OverviewFragment.kt" في نهاية الفئة، نفِّذ طريقة onOptionsItemSelected() للتعامل مع اختيارات عناصر القائمة.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
} 
  1. في onOptionsItemSelected()، استدعِ الدالة updateFilter() في نموذج العرض باستخدام الفلتر المناسب. استخدِم كتلة when {} في Kotlin للتبديل بين الخيارات. استخدِم MarsApiFilter.SHOW_ALL لقيمة الفلتر التلقائية. عليك عرض true لأنّك تعاملت مع عنصر القائمة. استيراد MarsApiFilter (com.example.android.marsrealestate.network.MarsApiFilter) عند الطلب يتم عرض طريقة onOptionsItemSelected() الكاملة أدناه.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
   viewModel.updateFilter(
           when (item.itemId) {
               R.id.show_rent_menu -> MarsApiFilter.SHOW_RENT
               R.id.show_buy_menu -> MarsApiFilter.SHOW_BUY
               else -> MarsApiFilter.SHOW_ALL
           }
   )
   return true
}
  1. يمكنك تجميع التطبيق وتشغيله. سيطلق التطبيق شبكة النظرة العامة الأولى التي تتضمّن جميع أنواع الأماكن المتاحة للاستئجار، مع وضع علامة الدولار على الأماكن المتاحة للبيع.
  2. اختَر استئجار من قائمة الخيارات. تتم إعادة تحميل المواقع ولا يظهر أي منها مع رمز الدولار. (يتم عرض الأماكن المخصّصة للاستئجار فقط). قد تحتاج إلى الانتظار بضع لحظات إلى أن تتم إعادة تحميل العرض ليظهر فقط المواقع التي تمّت فلترتها.
  3. اختَر شراء من قائمة الخيارات. تتم إعادة تحميل المواقع مرة أخرى، وتظهر جميعها مع رمز الدولار. (يتم عرض العقارات المعروضة للبيع فقط).

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

يتم تشغيل هذه الفئة عندما ينقر المستخدم على صورة في شبكة النظرة العامة. لإجراء ذلك، عليك إضافة أداة معالجة onClick إلى عناصر شبكة RecyclerView، ثم الانتقال إلى الجزء الجديد. يمكنك التنقّل من خلال إحداث تغيير في LiveData ViewModel، كما فعلت طوال هذه الدروس. يمكنك أيضًا استخدام مكوّن Safe Args الإضافي في Navigation لتمرير معلومات MarsProperty المحدّدة من جزء النظرة العامة إلى جزء التفاصيل.

الخطوة 1: إنشاء نموذج عرض التفاصيل وتعديل تخطيط التفاصيل

على غرار العملية التي استخدمتها لنموذج عرض النظرة العامة والقصاصات، عليك الآن تنفيذ نموذج العرض وملفات التنسيق لقصاصة التفاصيل.

  1. فتح "detail/DetailViewModel.kt" وكما أنّ ملفات Kotlin ذات الصلة بالشبكة مضمّنة في المجلد network وملفات النظرة العامة في overview، يحتوي المجلد detail على الملفات المرتبطة بعرض التفاصيل. لاحظ أنّ الفئة DetailViewModel (الفارغة حاليًا) تأخذ marsProperty كمعلَمة في الدالة الإنشائية.
class DetailViewModel( marsProperty: MarsProperty,
                     app: Application) : AndroidViewModel(app) {
}
  1. داخل تعريف الفئة، أضِف LiveData لسمة Mars المحدّدة، وذلك لعرض هذه المعلومات في طريقة العرض التفصيلية. اتّبِع النمط المعتاد لإنشاء MutableLiveData يحتوي على MarsProperty نفسه، ثمّ اعرض خاصية LiveData عامة غير قابلة للتغيير.

    استيراد androidx.lifecycle.LiveData واستيراد androidx.lifecycle.MutableLiveData عند الطلب.
private val _selectedProperty = MutableLiveData<MarsProperty>()
val selectedProperty: LiveData<MarsProperty>
   get() = _selectedProperty
  1. أنشئ حزمة init {} واضبط قيمة سمة Mars المحدّدة باستخدام العنصر MarsProperty من الدالة الإنشائية.
    init {
        _selectedProperty.value = marsProperty
    }
  1. افتح res/layout/fragment_detail.xml واطّلِع عليه في عرض التصميم.

    هذا هو ملف التصميم الخاص بقطعة التفاصيل. تحتوي على ImageView للصورة الكبيرة، وTextView لنوع المكان (للاستئجار أو البيع)، وTextView للسعر. يُرجى العِلم أنّ تخطيط القيود مضمّن في ScrollView، لذا سيتم التمرير تلقائيًا إذا أصبح العرض كبيرًا جدًا بالنسبة إلى الشاشة، مثلاً عندما يعرضه المستخدم في الوضع الأفقي.
  2. انتقِل إلى علامة التبويب نص للاطّلاع على التنسيق. في أعلى التصميم، قبل العنصر <ScrollView> مباشرةً، أضِف العنصر <data> لربط نموذج عرض التفاصيل بالتصميم.
<data>
   <variable
       name="viewModel"
       type="com.example.android.marsrealestate.detail.DetailViewModel" />
</data>
  1. أضِف السمة app:imageUrl إلى العنصر ImageView. اضبطها على imgSrcUrl من الموقع المحدّد لنموذج العرض.

    سيتم استخدام أداة ربط البيانات التي تحمّل صورة باستخدام Glide تلقائيًا هنا أيضًا، لأنّ هذه الأداة تراقب جميع سمات app:imageUrl.
 app:imageUrl="@{viewModel.selectedProperty.imgSrcUrl}"

الخطوة 2: تحديد التنقّل في نموذج عرض النظرة العامة

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

  1. فتح "overview/OverviewViewModel.kt" أضِف السمة _navigateToSelectedProperty MutableLiveData واعرضها باستخدام LiveData غير قابل للتغيير.

    عندما يتغيّر هذا LiveData إلى قيمة غير فارغة، يتم تشغيل التنقّل. (ستضيف قريبًا الرمز البرمجي لمراقبة هذا المتغيّر وتفعيل التنقّل).
private val _navigateToSelectedProperty = MutableLiveData<MarsProperty>()
val navigateToSelectedProperty: LiveData<MarsProperty>
   get() = _navigateToSelectedProperty
  1. في نهاية الفئة، أضِف طريقة displayPropertyDetails() التي تضبط _navigateToSelectedProperty على موقع Mars المحدّد.
fun displayPropertyDetails(marsProperty: MarsProperty) {
   _navigateToSelectedProperty.value = marsProperty
}
  1. أضِف طريقة displayPropertyDetailsComplete() التي تضبط قيمة _navigateToSelectedProperty على القيمة الخالية. تحتاج إلى ذلك لوضع علامة على حالة التنقّل على أنّها مكتملة، ولتجنُّب إعادة تشغيل التنقّل عندما يعود المستخدم من عرض التفاصيل.
fun displayPropertyDetailsComplete() {
   _navigateToSelectedProperty.value = null
}

الخطوة 3: إعداد أدوات معالجة النقرات في أداة ربط الشبكة والجزء

  1. فتح "overview/PhotoGridAdapter.kt" في نهاية الصف، أنشئ صفًا مخصّصًا OnClickListener يأخذ تعبير lambda مع مَعلمة marsProperty. داخل الفئة، حدِّد الدالة onClick() التي تم ضبطها على مَعلمة lambda.
class OnClickListener(val clickListener: (marsProperty:MarsProperty) -> Unit) {
     fun onClick(marsProperty:MarsProperty) = clickListener(marsProperty)
}
  1. انتقِل للأعلى إلى تعريف الفئة PhotoGridAdapter، وأضِف السمة الخاصة OnClickListener إلى الدالة الإنشائية.
class PhotoGridAdapter( private val onClickListener: OnClickListener ) :
       ListAdapter<MarsProperty,              
           PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
  1. اجعل الصورة قابلة للنقر من خلال إضافة onClickListener إلى عنصر الشبكة في طريقة onBindviewHolder(). حدِّد أداة معالجة النقرات بين طلبات getItem() and bind().
override fun onBindViewHolder(holder: MarsPropertyViewHolder, position: Int) {
   val marsProperty = getItem(position)
   holder.itemView.setOnClickListener {
       onClickListener.onClick(marsProperty)
   }
   holder.bind(marsProperty)
}
  1. فتح "overview/OverviewFragment.kt" في طريقة onCreateView()، استبدِل السطر الذي يبدأ السمة binding.photosGrid.adapter بالسطر الموضّح أدناه.

    يضيف هذا الرمز العنصر PhotoGridAdapter.onClickListener إلى الدالة الإنشائية PhotoGridAdapter، ويستدعي viewModel.displayPropertyDetails() باستخدام العنصر MarsProperty الذي تم تمريره. يؤدي ذلك إلى تشغيل LiveData في نموذج العرض للتنقّل.
binding.photosGrid.adapter = PhotoGridAdapter(PhotoGridAdapter.OnClickListener {
   viewModel.displayPropertyDetails(it)
})

الخطوة 4: تعديل الرسم البياني للتنقّل وجعل MarsProperty قابلاً للتسلسل

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

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

  1. فتح "res/navigation/nav_graph.xml" انقر على علامة التبويب نص لعرض رمز XML الخاص برسم التنقّل.
  2. داخل العنصر <fragment> الخاص بـ "جزء التفاصيل"، أضِف العنصر <argument> الموضّح أدناه. تتضمّن هذه الوسيطة، التي تُسمّى selectedProperty، النوع MarsProperty.
<argument
   android:name="selectedProperty"
   app:argType="com.example.android.marsrealestate.network.MarsProperty"
   />
  1. يمكنك تجميع التطبيق. ستظهر لك رسالة خطأ في التنقّل لأنّ MarsProperty ليس قابلاً للتسلسل. تتيح واجهة Parcelable إمكانية تسلسل العناصر، ما يتيح نقل بيانات العناصر بين الأجزاء أو الأنشطة. في هذه الحالة، لكي يتم تمرير البيانات داخل العنصر MarsProperty إلى جزء التفاصيل من خلال Safe Args، يجب أن تنفّذ MarsProperty الواجهة Parcelable. والخبر السار هو أنّ Kotlin توفّر اختصارًا سهلاً لتنفيذ هذه الواجهة.
  2. فتح "network/MarsProperty.kt" أضِف التعليق التوضيحي @Parcelize إلى تعريف الفئة.

    استورِد kotlinx.android.parcel.Parcelize عند الطلب.

    يستخدم التعليق التوضيحي @Parcelize إضافات Kotlin Android لتنفيذ الطرق تلقائيًا في واجهة Parcelable لهذه الفئة. ليس عليك اتّخاذ أي إجراء آخر.
@Parcelize
data class MarsProperty (
  1. غيِّر تعريف الفئة MarsProperty لتوسيع Parcelable.

    استورِد android.os.Parcelable عند الطلب.

    يبدو تعريف الفئة MarsProperty الآن على النحو التالي:
@Parcelize
data class MarsProperty (
       val id: String,
       @Json(name = "img_src") val imgSrcUrl: String,
       val type: String,
       val price: Double) : Parcelable {

الخطوة 5: ربط الأجزاء

لن تتنقّل بعد، بل سيتم التنقّل الفعلي في الأجزاء. في هذه الخطوة، ستضيف الأجزاء الأخيرة لتنفيذ التنقّل بين الأجزاء الخاصة بالنظرة العامة والتفاصيل.

  1. فتح "overview/OverviewFragment.kt" في onCreateView()، أسفل الأسطر التي تهيئ أداة ربط شبكة الصور، أضِف الأسطر الموضّحة أدناه لمراقبة navigatedToSelectedProperty من نموذج عرض النظرة العامة.

    استيراد androidx.lifecycle.Observer واستيراد androidx.navigation.fragment.findNavController عند الطلب

    يتحقّق المراقب ممّا إذا كان MarsProperty، أي it في تعبير lambda، ليس فارغًا، وإذا كان كذلك، يحصل على أداة التحكّم في التنقّل من الجزء الذي يحتوي على findNavController(). استدعِ الدالة displayPropertyDetailsComplete() لإخبار نموذج العرض بإعادة ضبط LiveData إلى الحالة الفارغة، حتى لا يتم تشغيل التنقّل مرة أخرى عن طريق الخطأ عند عودة التطبيق إلى OverviewFragment.
viewModel.navigateToSelectedProperty.observe(this, Observer {
   if ( null != it ) {   
      this.findNavController().navigate(
              OverviewFragmentDirections.actionShowDetail(it))             
      viewModel.displayPropertyDetailsComplete()
   }
})
  1. فتح "detail/DetailFragment.kt" أضِف هذا السطر أسفل طلب setLifecycleOwner() مباشرةً في طريقة onCreateView(). يحصل هذا السطر على عنصر MarsProperty المحدّد من Safe Args.

    لاحظ استخدام عامل تأكيد عدم القيمة الفارغة في Kotlin (!!). إذا لم يكن selectedProperty متوفّرًا، يعني ذلك حدوث خطأ فادح، ويجب أن يعرض الرمز خطأ مؤشر فارغ. (في رمز الإنتاج، يجب التعامل مع هذا الخطأ بطريقة ما).
 val marsProperty = DetailFragmentArgs.fromBundle(arguments!!).selectedProperty
  1. أضِف هذا السطر بعد ذلك للحصول على DetailViewModelFactory جديدة. ستستخدم DetailViewModelFactory للحصول على مثيل من DetailViewModel. يتضمّن التطبيق الأوّلي عملية تنفيذ DetailViewModelFactory، لذا كل ما عليك فعله هنا هو إعداده.
val viewModelFactory = DetailViewModelFactory(marsProperty, application)
  1. أخيرًا، أضِف هذا الخط للحصول على DetailViewModel من المصنع وربط جميع الأجزاء.
      binding.viewModel = ViewModelProviders.of(
                this, viewModelFactory).get(DetailViewModel::class.java)
  1. يمكنك تجميع التطبيق وتشغيله، ثم النقر على أي صورة لمكان على كوكب المريخ. يظهر جزء التفاصيل الخاص بتفاصيل الموقع. انقر على زر "الرجوع" للعودة إلى صفحة النظرة العامة، ولاحظ أنّ شاشة التفاصيل لا تزال فارغة إلى حدّ ما. يمكنك الانتهاء من إضافة بيانات الموقع إلى صفحة التفاصيل هذه في المهمة التالية.

في الوقت الحالي، تعرض صفحة التفاصيل صورة المريخ نفسها التي اعتدت رؤيتها في صفحة النظرة العامة. يحتوي الصف MarsProperty أيضًا على نوع الموقع (للاستئجار أو الشراء) وسعر الموقع. يجب أن تتضمّن شاشة التفاصيل كلتا القيمتَين، وسيكون من المفيد أن تشير الأماكن المتاحة للاستئجار إلى أنّ السعر هو قيمة شهرية. يمكنك استخدام عمليات تحويل LiveData في نموذج العرض لتنفيذ كلّ من هذين الأمرين.

  1. فتح "res/values/strings.xml" يتضمّن الرمز الأولي موارد السلاسل، الموضّحة أدناه، لمساعدتك في إنشاء السلاسل الخاصة بعرض التفاصيل. بالنسبة إلى السعر، ستستخدم المرجع display_price_monthly_rental أو المرجع display_price، وذلك حسب نوع السمة.
<string name="type_rent">Rent</string>
<string name="type_sale">Sale</string>
<string name="display_type">For %s</string>
<string name="display_price_monthly_rental">$%,.0f/month</string>
<string name="display_price">$%,.0f</string>
  1. فتح "detail/DetailViewModel.kt" في أسفل الصف، أضِف الرمز البرمجي الموضّح أدناه.

    استيراد androidx.lifecycle.Transformations إذا طُلب منك ذلك

    يختبر هذا التحويل ما إذا كان المكان المحدّد مخصّصًا للاستئجار، وذلك باستخدام الاختبار نفسه من المهمة الأولى. إذا كانت السمة عبارة عن مكان مخصّص للاستئجار، سيختار التحويل السلسلة المناسبة من الموارد باستخدام مفتاح تبديل when {} في Kotlin. يجب أن ينتهي كل من هذين السلسلتين برقم، لذا عليك ربط property.price بعد ذلك.
val displayPropertyPrice = Transformations.map(selectedProperty) {
   app.applicationContext.getString(
           when (it.isRental) {
               true -> R.string.display_price_monthly_rental
               false -> R.string.display_price
           }, it.price)
}
  1. استورِد فئة R التي تم إنشاؤها للوصول إلى موارد السلسلة في المشروع.
import com.example.android.marsrealestate.R
  1. بعد عملية التحويل displayPropertyPrice، أضِف الرمز البرمجي الموضّح أدناه. يجمع هذا التحويل عدّة موارد سلاسل، استنادًا إلى ما إذا كان نوع المكان المخصّص للاستئجار.
val displayPropertyType = Transformations.map(selectedProperty) {
   app.applicationContext.getString(R.string.display_type,
           app.applicationContext.getString(
                   when (it.isRental) {
                       true -> R.string.type_rent
                       false -> R.string.type_sale
                   }))
}
  1. فتح "res/layout/fragment_detail.xml" ما عليك سوى تنفيذ خطوة أخرى، وهي ربط السلاسل الجديدة (التي أنشأتها باستخدام عمليات التحويل LiveData) بطريقة العرض التفصيلية. لإجراء ذلك، اضبط قيمة الحقل النصي لنوع العقار على viewModel.displayPropertyType، والحقل النصي لقيمة السعر على viewModel.displayPropertyPrice.
<TextView
   android:id="@+id/property_type_text"
...
android:text="@{viewModel.displayPropertyType}"
...
   tools:text="To Rent" />

<TextView
   android:id="@+id/price_value_text"
...
android:text="@{viewModel.displayPropertyPrice}"
...
   tools:text="$100,000" />
  1. يمكنك تجميع التطبيق وتشغيله. ستظهر الآن جميع بيانات الموقع في صفحة التفاصيل بتنسيق جيد.

مشروع "استوديو Android": MarsRealEstateFinal

تعبيرات الربط

  • استخدِم تعبيرات الربط في ملفات تنسيق XML لتنفيذ عمليات برمجية بسيطة، مثل العمليات الرياضية أو الاختبارات الشرطية، على البيانات المرتبطة.
  • للإشارة إلى الفئات داخل ملف التصميم، استخدِم العلامة <import> داخل العلامة <data>.

خيارات طلبات البحث في خدمات الويب

  • يمكن أن تتضمّن الطلبات المقدَّمة إلى خدمات الويب مَعلمات اختيارية.
  • لتحديد مَعلمات الطلب في الطلب، استخدِم التعليق التوضيحي @Query في Retrofit.

دورة Udacity التدريبية:

مستندات مطوّري تطبيقات Android:

غير ذلك:

يسرد هذا القسم مهامًا منزلية محتملة للطلاب الذين يعملون على هذا الدرس التطبيقي العملي كجزء من دورة تدريبية يقودها مدرّب. على المعلّم تنفيذ ما يلي:

  • حدِّد واجبًا منزليًا إذا لزم الأمر.
  • توضيح كيفية إرسال الواجبات المنزلية للطلاب
  • وضع درجات للواجبات المنزلية

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

إذا كنت تعمل على هذا الدرس العملي بنفسك، يمكنك استخدام مهام الواجب المنزلي هذه لاختبار معلوماتك.

الإجابة عن هذه الأسئلة

السؤال 1

ما هي وظيفة العلامة <import> في ملف تصميم XML؟

▢ تضمين ملف تصميم واحد في ملف آخر

▢ تضمين رمز Kotlin داخل ملف التصميم

▢ توفير إمكانية الوصول إلى السمات المرتبطة بالبيانات

▢ السماح لك بالإشارة إلى الصفوف وأفرادها في عبارات الربط

السؤال 2

كيف يمكن إضافة خيار طلب بحث إلى طلب خدمة ويب REST في Retrofit؟

▢ إلحاق طلب البحث بنهاية عنوان URL للطلب

▢ أضِف مَعلمة للطلب إلى الدالة التي تُجري الطلب، وأضِف تعليقًا توضيحيًا إلى هذه المَعلمة باستخدام @Query.

▢ استخدِم فئة Query لإنشاء طلب.

▢ استخدِم طريقة addQuery() في أداة إنشاء Retrofit.

بدء الدرس التالي: 9.1: المستودع

للحصول على روابط تؤدي إلى دروس تطبيقية أخرى في هذه الدورة التدريبية، اطّلِع على الصفحة المقصودة الخاصة بالدروس التطبيقية حول أساسيات Android Kotlin.