‫Android Kotlin Fundamentals 08.2: تحميل الصور وعرضها من الإنترنت

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

مقدمة

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

ما يجب معرفته

  • كيفية إنشاء واستخدام الأجزاء
  • كيفية استخدام مكوّنات البنية، بما في ذلك نماذج العرض، ومصانع نماذج العرض، وعمليات التحويل، وLiveData
  • كيفية استرداد JSON من خدمة ويب REST وتحليل هذه البيانات إلى عناصر Kotlin باستخدام مكتبتَي Retrofit وMoshi
  • كيفية إنشاء تخطيط شبكي باستخدام RecyclerView
  • طريقة عمل Adapter وViewHolder وDiffUtil

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

  • كيفية استخدام مكتبة Glide لتحميل صورة وعرضها من عنوان URL على الويب
  • كيفية استخدام RecyclerView ومحوّل شبكة لعرض شبكة من الصور
  • كيفية التعامل مع الأخطاء المحتملة أثناء تنزيل الصور وعرضها

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

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

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

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

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

لحسن الحظ، يمكنك استخدام مكتبة Glide التي طوّرها المنتدى لتنزيل صورك وتخزينها مؤقتًا وفك ترميزها وتخزينها في ذاكرة التخزين المؤقت. يوفّر عليك Glide الكثير من العمل مقارنةً بما إذا كان عليك تنفيذ كل ذلك من البداية.

يحتاج تطبيق Glide إلى أمرين أساسيين:

  • عنوان URL للصورة التي تريد تحميلها وعرضها.
  • عنصر ImageView لعرض تلك الصورة

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

الخطوة 1: إضافة تبعية Glide

  1. افتح تطبيق MarsRealEstate من آخر تجربة عملية. (يمكنك تنزيل تطبيق MarsRealEstateNetwork من هنا إذا لم يكن مثبّتًا لديك).
  2. شغِّل التطبيق لمعرفة ما يمكنه فعله. (تعرض هذه السمة تفاصيل نصية عن عقار متوفّر افتراضيًا على كوكب المريخ).
  3. افتح ملف build.gradle (وحدة التطبيق).
  4. في القسم dependencies، أضِف السطر التالي لمكتبة Glide:
implementation "com.github.bumptech.glide:glide:$version_glide"


يُرجى العِلم أنّه تم تحديد رقم الإصدار بشكل منفصل في ملف Gradle الخاص بالمشروع.

  1. انقر على المزامنة الآن لإعادة إنشاء المشروع باستخدام التبعية الجديدة.

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

بعد ذلك، عدِّل الفئة OverviewViewModel لتضمين بيانات مباشرة لموقع واحد على كوكب المريخ.

  1. فتح "overview/OverviewViewModel.kt" أسفل LiveData الخاص بـ _response مباشرةً، أضِف بيانات البث المباشر الداخلية (القابلة للتغيير) والخارجية (غير القابلة للتغيير) لكائن MarsProperty واحد.

    استيراد الصف MarsProperty (com.example.android.marsrealestate.network.MarsProperty) عند الطلب
private val _property = MutableLiveData<MarsProperty>()

val property: LiveData<MarsProperty>
   get() = _property
  1. في طريقة getMarsRealEstateProperties()، ابحث عن السطر داخل الحظر try/catch {} الذي يضبط _response.value على عدد المواقع. أضِف الاختبار الموضّح أدناه. في حال توفّر عناصر MarsProperty، يضبط هذا الاختبار قيمة _property LiveData على السمة الأولى في listResult.
if (listResult.size > 0) {   
    _property.value = listResult[0]
}

يبدو الآن حظر try/catch {} الكامل على النحو التالي:

try {
   var listResult = getPropertiesDeferred.await()
   _response.value = "Success: ${listResult.size} Mars properties retrieved"
   if (listResult.size > 0) {      
       _property.value = listResult[0]
   }
 } catch (e: Exception) {
    _response.value = "Failure: ${e.message}"
 }
  1. افتح ملف res/layout/fragment_overview.xml. في العنصر <TextView>، غيِّر android:text للربط بالمكوّن imgSrcUrl من property LiveData:
android:text="@{viewModel.property.imgSrcUrl}"
  1. شغِّل التطبيق. لا يعرض TextView سوى عنوان URL للصورة في الموقع الأول على كوكب المريخ. كل ما فعلته حتى الآن هو إعداد نموذج العرض والبيانات النشطة لعنوان URL هذا.

الخطوة 3: إنشاء أداة ربط البيانات واستدعاء Glide

الآن لديك عنوان URL لصورة تريد عرضها، وحان الوقت لبدء استخدام Glide لتحميل هذه الصورة. في هذه الخطوة، ستستخدم برنامج ربط للحصول على عنوان URL من سمة XML مرتبطة بعنصر ImageView، وستستخدم Glide لتحميل الصورة. محوّلات الربط هي طرق إضافية تقع بين طريقة العرض والبيانات المرتبطة لتوفير سلوك مخصّص عند تغيير البيانات. في هذه الحالة، يكون السلوك المخصّص هو استدعاء Glide لتحميل صورة من عنوان URL إلى ImageView.

  1. فتح "BindingAdapters.kt" سيحتوي هذا الملف على أدوات ربط البيانات التي تستخدمها في جميع أنحاء التطبيق.
  2. أنشئ الدالة bindImage() التي تأخذ ImageView وString كمعلَمات. أضِف التعليق التوضيحي @BindingAdapter إلى الدالة. تخبر التعليق التوضيحي @BindingAdapter ميزة ربط البيانات بأنّك تريد تنفيذ أداة ربط البيانات هذه عندما يحتوي عنصر XML على السمة imageUrl.

    استيراد androidx.databinding.BindingAdapter وandroid.widget.ImageView عند الطلب
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {

}
  1. داخل الدالة bindImage()، أضِف مجموعة let {} للوسيطة imgUrl:
imgUrl?.let { 
}
  1. داخل الحظر let {}، أضِف السطر الموضّح أدناه لتحويل سلسلة عنوان URL (من ملف XML) إلى عنصر Uri. استورِد androidx.core.net.toUri عند الطلب.

    أنت تريد أن يستخدم العنصر Uri النهائي مخطط HTTPS، لأنّ الخادم الذي تستردّ منه الصور يتطلّب هذا المخطط. لاستخدام مخطط HTTPS، أضِف buildUpon.scheme("https") إلى أداة إنشاء toUri. الطريقة toUri() هي دالة إضافة في Kotlin من مكتبة Android KTX الأساسية، لذا يبدو أنّها جزء من الفئة String.
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
  1. لا يزال داخل let {}، استدعِ Glide.with() لتحميل الصورة من العنصر Uri إلى ImageView. استورِد com.bumptech.glide.Glide عند الطلب.
Glide.with(imgView.context)
       .load(imgUri)
       .into(imgView)

الخطوة 4: تعديل التنسيق والقصاصات

على الرغم من أنّ Glide حمّل الصورة، لا يوجد ما يمكن رؤيته حتى الآن. الخطوة التالية هي تعديل التنسيق والأجزاء باستخدام ImageView لعرض الصورة.

  1. فتح "res/layout/gridview_item.xml" هذا هو ملف مصدر التنسيق الذي ستستخدمه لكل عنصر في RecyclerView لاحقًا في الدرس العملي. يمكنك استخدامها مؤقتًا هنا لعرض الصورة الفردية فقط.
  2. أضِف عنصر <data> لربط البيانات فوق عنصر <ImageView>، واربطه بفئة OverviewViewModel:
<data>
   <variable
       name="viewModel"
       type="com.example.android.marsrealestate.overview.OverviewViewModel" />
</data>
  1. أضِف السمة app:imageUrl إلى العنصر ImageView لاستخدام أداة ربط تحميل الصور الجديدة:
app:imageUrl="@{viewModel.property.imgSrcUrl}"
  1. فتح "overview/OverviewFragment.kt" في طريقة onCreateView()، علِّق على السطر الذي يوسّع الفئة FragmentOverviewBinding ويعيّنها لمتغيّر الربط. هذا التغيير مؤقّت وسيعود حسابك إلى الوضع السابق لاحقًا.
//val binding = FragmentOverviewBinding.inflate(inflater)
  1. أضِف سطرًا لتضخيم فئة GridViewItemBinding بدلاً من ذلك. استورِد com.example.android.marsrealestate. databinding.GridViewItemBinding عند الطلب.
val binding = GridViewItemBinding.inflate(inflater)
  1. شغِّل التطبيق. من المفترض أن تظهر لك الآن صورة من MarsProperty في قائمة النتائج.

الخطوة 5: إضافة صور بسيطة للتحميل والأخطاء

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

  1. افتح res/drawable/ic_broken_image.xml، ثم انقر على علامة التبويب معاينة على يسار الصفحة. بالنسبة إلى صورة الخطأ، يمكنك استخدام رمز الصورة المعطّلة المتوفّر في مكتبة الرموز المضمّنة. يستخدم هذا الرسم المتّجه القابل للرسم السمة android:tint لتلوين الرمز باللون الرمادي.

  1. فتح "res/drawable/loading_animation.xml" هذا العنصر القابل للرسم هو صورة متحركة يتم تحديدها باستخدام العلامة <animate-rotate>. تدوّر الصورة المتحركة عنصرًا قابلاً للرسم، loading_img.xml، حول نقطة المنتصف. (لا تظهر الصورة المتحركة في المعاينة).

  1. الرجوع إلى ملف BindingAdapters.kt في طريقة bindImage()، عدِّل الاستدعاء إلى Glide.with() لاستدعاء الدالة apply() بين load() وinto(). استورِد com.bumptech.glide.request.RequestOptions عند الطلب.

    يضبط هذا الرمز صورة التحميل الخاصة بالعنصر النائب لاستخدامها أثناء التحميل (العنصر القابل للرسم loading_animation). يضبط الرمز أيضًا صورة لاستخدامها في حال تعذّر تحميل الصورة (العنصر القابل للرسم broken_image). يبدو الآن أسلوب bindImage() الكامل على النحو التالي:
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
    imgUrl?.let {
        val imgUri = 
           imgUrl.toUri().buildUpon().scheme("https").build()
        Glide.with(imgView.context)
                .load(imgUri)
                .apply(RequestOptions()
                        .placeholder(R.drawable.loading_animation)
                        .error(R.drawable.ic_broken_image))
                .into(imgView)
    }
}
  1. شغِّل التطبيق. استنادًا إلى سرعة اتصالك بالشبكة، قد تظهر لك صورة التحميل لفترة وجيزة أثناء تنزيل Glide لصورة المكان وعرضها. ولكن لن يظهر لك رمز الصورة المعطّلة بعد، حتى إذا أوقفت شبكتك، لأنّك ستصلح ذلك في الجزء الأخير من الدرس العملي.

يحمّل تطبيقك الآن معلومات الموقع من الإنترنت. باستخدام البيانات من عنصر القائمة الأول MarsProperty، أنشأت السمة LiveData في نموذج العرض، واستخدمت عنوان URL للصورة من بيانات هذه السمة لملء ImageView. ولكن الهدف هو أن يعرض تطبيقك شبكة من الصور، لذا عليك استخدام RecyclerView مع GridLayoutManager.

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

يحتوي نموذج العرض حاليًا على _property LiveData يتضمّن عنصر MarsProperty واحدًا، وهو العنصر الأول في قائمة الردود من خدمة الويب. في هذه الخطوة، يمكنك تغيير LiveData لتضمين القائمة الكاملة لعناصر MarsProperty.

  1. فتح "overview/OverviewViewModel.kt"
  2. غيِّر المتغيّر الخاص _property إلى _properties. غيِّر النوع إلى قائمة بعناصر MarsProperty.
private val _properties = MutableLiveData<List<MarsProperty>>()
  1. استبدِل البيانات المباشرة الخارجية property بـ properties. أضِف القائمة إلى النوع LiveData هنا أيضًا:
 val properties: LiveData<List<MarsProperty>>
        get() = _properties
  1. انتقِل إلى طريقة getMarsRealEstateProperties(). داخل الحظر try {}، استبدِل النص الكامل الذي أضفته في المهمة السابقة بالسطر الموضّح أدناه. بما أنّ المتغيّر listResult يتضمّن قائمة بعناصر MarsProperty، يمكنك فقط تعيينه إلى _properties.value بدلاً من اختبار استجابة ناجحة.
_properties.value = listResult

يبدو الآن حظر try/catch بأكمله على النحو التالي:

try {
   var listResult = getPropertiesDeferred.await()
   _response.value = "Success: ${listResult.size} Mars properties retrieved"
   _properties.value = listResult
} catch (e: Exception) {
   _response.value = "Failure: ${e.message}"
}

الخطوة 2: تعديل التصاميم والرموز

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

  1. فتح "res/layout/gridview_item.xml" غيِّر ربط البيانات من OverviewViewModel إلى MarsProperty، وأعِد تسمية المتغيّر إلى "property".
<variable
   name="property"
   type="com.example.android.marsrealestate.network.MarsProperty" />
  1. في <ImageView>، غيِّر السمة app:imageUrl للإشارة إلى عنوان URL للصورة في العنصر MarsProperty:
app:imageUrl="@{property.imgSrcUrl}"
  1. فتح "overview/OverviewFragment.kt" في onCreateview()، أزِل التعليق من السطر الذي يوسّع FragmentOverviewBinding. احذف السطر الذي يوسّع GridViewBinding أو علِّق عليه. تؤدي هذه التغييرات إلى التراجع عن التغييرات المؤقتة التي أجريتها في المهمة الأخيرة.
val binding = FragmentOverviewBinding.inflate(inflater)
 // val binding = GridViewItemBinding.inflate(inflater)
  1. فتح "res/layout/fragment_overview.xml" احذف عنصر <TextView> بالكامل.
  2. أضِف عنصر <RecyclerView> هذا بدلاً من ذلك، والذي يستخدم GridLayoutManager وتنسيق grid_view_item لعنصر واحد:
<androidx.recyclerview.widget.RecyclerView
            android:id="@+id/photos_grid"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:padding="6dp"
            android:clipToPadding="false"
            app:layoutManager=
               "androidx.recyclerview.widget.GridLayoutManager"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:spanCount="2"
            tools:itemCount="16"
            tools:listitem="@layout/grid_view_item" />

الخطوة 3: إضافة أداة ربط شبكة الصور

يحتوي التنسيق fragment_overview الآن على RecyclerView، بينما يحتوي التنسيق grid_view_item على ImageView واحد. في هذه الخطوة، يمكنك ربط البيانات بـ RecyclerView من خلال محوّل RecyclerView.

  1. فتح "overview/PhotoGridAdapter.kt"
  2. أنشئ الفئة PhotoGridAdapter باستخدام مَعلمات الدالة الإنشائية الموضّحة أدناه. يمتد الصف PhotoGridAdapter إلى ListAdapter، الذي يحتاج منشئه إلى نوع عنصر القائمة وحاوية العرض وتنفيذ DiffUtil.ItemCallback.

    استورِد الصفَّين androidx.recyclerview.widget.ListAdapter وcom.example.android.marsrealestate.network.MarsProperty عند الطلب. في الخطوات التالية، ستنفّذ الأجزاء الأخرى الناقصة من أداة الإنشاء هذه التي تتسبّب في حدوث أخطاء.
class PhotoGridAdapter : ListAdapter<MarsProperty,
        PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {

}
  1. انقر في أي مكان في الفئة PhotoGridAdapter واضغط على Control+i لتنفيذ طريقتَي ListAdapter، وهما onCreateViewHolder() وonBindViewHolder().
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhotoGridAdapter.MarsPropertyViewHolder {
   TODO("not implemented") 
}

override fun onBindViewHolder(holder: PhotoGridAdapter.MarsPropertyViewHolder, position: Int) {
   TODO("not implemented") 
}
  1. في نهاية تعريف الفئة PhotoGridAdapter، بعد الطرق التي أضفتها للتو، أضِف تعريف عنصر مصاحب لـ DiffCallback، كما هو موضّح أدناه.

    انقر على "استيراد" androidx.recyclerview.widget.DiffUtil عندما يُطلب منك ذلك.

    يمتد العنصر DiffCallback إلى DiffUtil.ItemCallback مع نوع العنصر الذي تريد مقارنته، وهو MarsProperty.
companion object DiffCallback : DiffUtil.ItemCallback<MarsProperty>() {
}
  1. اضغط على Control+i لتنفيذ طرق المقارنة لهذا العنصر، وهما areItemsTheSame() وareContentsTheSame().
override fun areItemsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
   TODO("not implemented") 
}

override fun areContentsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
   TODO("not implemented") }
  1. بالنسبة إلى طريقة areItemsTheSame()، أزِل TODO. استخدِم عامل المساواة المرجعية في Kotlin (===)، الذي يعرض القيمة true إذا كانت مراجع العناصر لكل من oldItem وnewItem هي نفسها.
override fun areItemsTheSame(oldItem: MarsProperty, 
                  newItem: MarsProperty): Boolean {
   return oldItem === newItem
}
  1. بالنسبة إلى areContentsTheSame()، استخدِم عامل المساواة العادي على معرّف oldItem وnewItem فقط.
override fun areContentsTheSame(oldItem: MarsProperty, 
                  newItem: MarsProperty): Boolean {
   return oldItem.id == newItem.id
}
  1. داخل الفئة PhotoGridAdapter، أسفل العنصر المصاحب، أضِف تعريف فئة داخلية لـ MarsPropertyViewHolder، والذي يوسّع RecyclerView.ViewHolder.

    استورِد androidx.recyclerview.widget.RecyclerView وcom.example.android.marsrealestate.databinding.GridViewItemBinding عند الطلب.

    تحتاج إلى المتغيّر GridViewItemBinding لربط MarsProperty بالتصميم، لذا مرِّر المتغيّر إلى MarsPropertyViewHolder. بما أنّ فئة ViewHolder الأساسية تتطلّب عرضًا في الدالة الإنشائية، عليك تمرير العرض الأساسي للربط إليها.
class MarsPropertyViewHolder(private var binding: 
                   GridViewItemBinding):
       RecyclerView.ViewHolder(binding.root) {

}
  1. في MarsPropertyViewHolder، أنشئ طريقة bind() تأخذ كائن MarsProperty كوسيطة وتضبط binding.property على هذا الكائن. استدعِ الدالة executePendingBindings() بعد ضبط السمة، ما يؤدي إلى تنفيذ التعديل على الفور.
fun bind(marsProperty: MarsProperty) {
   binding.property = marsProperty
   binding.executePendingBindings()
}
  1. في onCreateViewHolder()، أزِل TODO وأضِف السطر الموضّح أدناه. استورِد android.view.LayoutInflater عند الطلب.

    يجب أن تعرض الطريقة onCreateViewHolder() MarsPropertyViewHolder جديدًا تم إنشاؤه من خلال توسيع GridViewItemBinding واستخدام LayoutInflater من سياق ViewGroup الأصل.
   return MarsPropertyViewHolder(GridViewItemBinding.inflate(
      LayoutInflater.from(parent.context)))
  1. في طريقة onBindViewHolder()، أزِل TODO وأضِف الأسطر الموضّحة أدناه. هنا، يمكنك استدعاء getItem() للحصول على عنصر MarsProperty المرتبط بموضع RecyclerView الحالي، ثم تمرير هذا الموقع إلى طريقة bind() في MarsPropertyViewHolder.
val marsProperty = getItem(position)
holder.bind(marsProperty)

الخطوة 4: إضافة أداة ربط البيانات وربط الأجزاء

أخيرًا، استخدِم BindingAdapter لتهيئة PhotoGridAdapter بقائمة كائنات MarsProperty. يؤدي استخدام BindingAdapter لضبط بيانات RecyclerView إلى جعل ربط البيانات يراقب LiveData تلقائيًا لقائمة عناصر MarsProperty. بعد ذلك، يتم استدعاء أداة الربط تلقائيًا عند تغيير القائمة MarsProperty.

  1. فتح "BindingAdapters.kt"
  2. في نهاية الملف، أضِف طريقة bindRecyclerView() تأخذ RecyclerView وقائمة بكائنات MarsProperty كمعلمات. أضِف التعليق التوضيحي @BindingAdapter إلى هذه الطريقة.

    استيراد androidx.recyclerview.widget.RecyclerView وcom.example.android.marsrealestate.network.MarsProperty عند الطلب
@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView, 
    data: List<MarsProperty>?) {
}
  1. داخل الدالة bindRecyclerView()، حوِّل recyclerView.adapter إلى PhotoGridAdapter، واستدعِ adapter.submitList() باستخدام البيانات. يُعلم هذا الإجراء RecyclerView عند توفّر قائمة جديدة.

استورِد com.example.android.marsrealestate.overview.PhotoGridAdapter عند الطلب.

val adapter = recyclerView.adapter as PhotoGridAdapter
adapter.submitList(data)
  1. فتح "res/layout/fragment_overview.xml" أضِف السمة app:listData إلى العنصر RecyclerView واضبطها على viewmodel.properties باستخدام ربط البيانات.
app:listData="@{viewModel.properties}"
  1. فتح "overview/OverviewFragment.kt" في onCreateView()، قبل طلب setHasOptionsMenu() مباشرةً، عليك إعداد محوّل RecyclerView في binding.photosGrid إلى عنصر PhotoGridAdapter جديد.
binding.photosGrid.adapter = PhotoGridAdapter()
  1. شغِّل التطبيق. من المفترض أن تظهر لك شبكة من صور MarsProperty. أثناء التنقّل للاطّلاع على صور جديدة، يعرض التطبيق رمز تقدّم التحميل قبل عرض الصورة نفسها. في حال تفعيل وضع الطائرة، ستظهر الصور التي لم يتم تحميلها بعد على شكل رموز صور تالفة.

يعرض تطبيق MarsRealEstate رمز الصورة المعطّلة عندما يتعذّر استرجاع صورة. ولكن عندما لا تتوفّر شبكة، يعرض التطبيق شاشة فارغة.

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

الخطوة 1: إضافة حالة إلى نموذج العرض

للبدء، يمكنك إنشاء LiveData في نموذج العرض لتمثيل حالة طلب الويب. هناك ثلاث حالات يجب أخذها في الاعتبار: التحميل والنجاح والفشل. تحدث حالة التحميل أثناء انتظار البيانات في طلب await().

  1. فتح "overview/OverviewViewModel.kt" في أعلى الملف (بعد عمليات الاستيراد، وقبل تعريف الفئة)، أضِف enum لتمثيل جميع الحالات المتاحة:
enum class MarsApiStatus { LOADING, ERROR, DONE }
  1. أعِد تسمية تعريفات البيانات المباشرة الداخلية والخارجية في جميع أنحاء فئة OverviewViewModel إلى _status._response بما أنّك أضفت دعمًا لـ _properties LiveData في وقت سابق من هذا الدرس العملي، لم يتم استخدام استجابة خدمة الويب الكاملة. أنت بحاجة إلى LiveData هنا لتتبُّع الحالة الحالية، لذا يمكنك إعادة تسمية المتغيّرات الحالية فقط.

غيِّر أيضًا الأنواع من String إلى MarsApiStatus..

private val _status = MutableLiveData<MarsApiStatus>()

val status: LiveData<MarsApiStatus>
   get() = _status
  1. انتقِل للأسفل إلى طريقة الدفع getMarsRealEstateProperties() وعدِّل _response إلى _status هنا أيضًا. غيِّر السلسلة "Success" إلى الحالة MarsApiStatus.DONE، والسلسلة "Failure" إلى MarsApiStatus.ERROR.
  2. أضِف حالة MarsApiStatus.LOADING إلى أعلى حزمة try {}، قبل استدعاء await(). هذه هي الحالة الأولية أثناء تشغيل الروتين الفرعي وانتظار البيانات. يبدو الآن حظر try/catch {} الكامل على النحو التالي:
try {
    _status.value = MarsApiStatus.LOADING
   var listResult = getPropertiesDeferred.await()
   _status.value = MarsApiStatus.DONE
   _properties.value = listResult
} catch (e: Exception) {
   _status.value = MarsApiStatus.ERROR
}
  1. بعد حالة الخطأ في الحظر catch {}، اضبط _properties LiveData على قائمة فارغة. سيؤدي ذلك إلى محو RecyclerView.
} catch (e: Exception) {
   _status.value = MarsApiStatus.ERROR
   _properties.value = ArrayList()
}

الخطوة 2: إضافة أداة ربط لـ ImageView الخاص بالحالة

الآن لديك حالة في نموذج العرض، ولكنّها مجرد مجموعة من الحالات. كيف يمكن إظهارها في التطبيق نفسه؟ في هذه الخطوة، ستستخدم ImageView مرتبطًا بربط البيانات لعرض رموز لحالتَي التحميل والخطأ. عندما يكون التطبيق في حالة التحميل أو حالة الخطأ، يجب أن يظهر ImageView. عند انتهاء تحميل التطبيق، يجب أن يكون الرمز ImageView غير مرئي.

  1. فتح "BindingAdapters.kt" أضِف أداة ربط جديدة باسم bindStatus() تأخذ قيمتَي ImageView وMarsApiStatus كمعلَمات. استورِد com.example.android.marsrealestate.overview.MarsApiStatus عند الطلب.
@BindingAdapter("marsApiStatus")
fun bindStatus(statusImageView: ImageView, 
          status: MarsApiStatus?) {
}
  1. أضِف when {} داخل طريقة bindStatus() للتبديل بين الحالات المختلفة.
when (status) {

}
  1. داخل when {}، أضِف حالة التحميل (MarsApiStatus.LOADING). بالنسبة إلى هذه الحالة، اضبط ImageView على "مرئي"، وعيِّن له الصورة المتحركة الخاصة بالتحميل. هذا هو رسم الحركة نفسه الذي استخدمته في Glide في المهمة السابقة. استورِد android.view.View عند الطلب.
when (status) {
   MarsApiStatus.LOADING -> {
      statusImageView.visibility = View.VISIBLE
      statusImageView.setImageResource(R.drawable.loading_animation)
   }
}
  1. أضِف حالة الخطأ، وهي MarsApiStatus.ERROR. على غرار ما فعلته للحالة LOADING، اضبط الحالة ImageView على مرئية وأعِد استخدام العنصر القابل للرسم connection-error.
MarsApiStatus.ERROR -> {
   statusImageView.visibility = View.VISIBLE
   statusImageView.setImageResource(R.drawable.ic_connection_error)
}
  1. أضِف حالة "تم"، وهي MarsApiStatus.DONE. في هذه الحالة، لديك استجابة ناجحة، لذا أوقِف إذن الوصول إلى الحالة ImageView لإخفائها.
MarsApiStatus.DONE -> {
   statusImageView.visibility = View.GONE
}

الخطوة 3: إضافة ImageView الخاص بالحالة إلى التصميم

  1. فتح "res/layout/fragment_overview.xml" أسفل العنصر RecyclerView، داخل ConstraintLayout، أضِف ImageView الموضّحة أدناه.

    تنطبق على ImageView هذه القيود نفسها التي تنطبق على RecyclerView. ومع ذلك، يستخدم العرض والارتفاع القيمة wrap_content لتوسيط الصورة بدلاً من توسيعها لملء العرض. لاحظ أيضًا السمة app:marsApiStatus التي تتضمّن طلب العرض BindingAdapter عند تغيير سمة الحالة في نموذج العرض.
<ImageView
   android:id="@+id/status_image"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:marsApiStatus="@{viewModel.status}" />
  1. فعِّل وضع الطيران في المحاكي أو الجهاز لمحاكاة عدم توفّر اتصال بالشبكة. جمِّع التطبيق وشغِّله، ولاحظ ظهور صورة الخطأ:

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

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

  • لتسهيل عملية إدارة الصور، استخدِم مكتبة Glide لتنزيل الصور وتخزينها مؤقتًا وفك ترميزها وتخزينها في ذاكرة التخزين المؤقت في تطبيقك.
  • يحتاج Glide إلى عنصرَين لتحميل صورة من الإنترنت: عنوان URL للصورة وعنصر ImageView لوضع الصورة فيه. لتحديد هذه الخيارات، استخدِم الطريقتَين load() وinto() مع Glide.
  • محوّلات الربط هي طرق إضافية تقع بين طريقة العرض والبيانات المرتبطة بطريقة العرض هذه. توفّر أدوات ربط البيانات سلوكًا مخصّصًا عند تغيُّر البيانات، مثلاً، لاستدعاء Glide لتحميل صورة من عنوان URL إلى ImageView.
  • محوّلات الربط هي طرق إضافية يتمّ وضع التعليق التوضيحي @BindingAdapter عليها.
  • لإضافة خيارات إلى طلب Glide، استخدِم طريقة apply(). على سبيل المثال، استخدِم apply() مع placeholder() لتحديد عنصر قابل للرسم خاص بالتحميل، واستخدِم apply() مع error() لتحديد عنصر قابل للرسم خاص بالخطأ.
  • لإنشاء شبكة من الصور، استخدِم RecyclerView مع GridLayoutManager.
  • لتعديل قائمة المواقع عند تغييرها، استخدِم أداة ربط بين RecyclerView والتنسيق.

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

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

غير ذلك:

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

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

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

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

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

السؤال 1

ما هي طريقة Glide التي تستخدمها للإشارة إلى ImageView الذي سيحتوي على الصورة التي تم تحميلها؟

into()

with()

imageview()

apply()

السؤال 2

كيف يمكن تحديد صورة عنصر نائب لعرضها أثناء تحميل Glide؟

▢ استخدام طريقة into() مع عنصر قابل للرسم

▢ استخدِم RequestOptions() واستدعِ طريقة placeholder() باستخدام عنصر قابل للرسم.

▢ تعيين السمة Glide.placeholder إلى عنصر قابل للرسم

▢ استخدِم RequestOptions() واستدعِ طريقة loadingImage() باستخدام عنصر قابل للرسم.

السؤال 3

كيف يمكن الإشارة إلى أنّ إحدى الطرق هي أداة ربط البيانات؟

▢ استدعِ طريقة setBindingAdapter() في LiveData.

‫▢ ضَع الطريقة في ملف Kotlin باسم BindingAdapters.kt.

‫▢ استخدِم السمة android:adapter في تخطيط XML.

▢ أضِف التعليق التوضيحي @BindingAdapter إلى الطريقة.

ابدأ الدرس التالي: ‫8.3 فلترة البيانات وعرض التفاصيل باستخدام بيانات الإنترنت

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