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

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

المقدّمة

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

ما يجب معرفته

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

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

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

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

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

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

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

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

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

تحتاج ميزة "الكتابة بالتمرير" بشكل أساسي إلى عنصرَين:

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

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

الخطوة 1: إضافة الاعتمادية بالتمرير

  1. افتح تطبيق Mars RealEstate من آخر درس تطبيقي حول الترميز. (يمكنك تنزيل Mars RealEstateNetwork هنا إذا لم يكن لديك التطبيق).
  2. شغِّل التطبيق لمعرفة وظائفه. (يعرض تفاصيل نصية لخاصية تتوفَّر افتراضيًا على كوكب المريخ).
  3. افتح build.gradle (الوحدة: التطبيق).
  4. في القسم dependencies، أضِف السطر التالي لمكتبة "الكتابة بالتمرير":
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 لصورة لعرضها، وحان الوقت لبدء العمل باستخدام "الكتابة بالتمرير" لتحميل تلك الصورة. في هذه الخطوة، يمكنك استخدام محوّل ربط للحصول على عنوان URL من إحدى سمات XML المرتبطة بعلامة ImageView، ويمكنك استخدام "الكتابة بالتمرير" لتحميل الصورة. محولات الربط هي طرق إضافة تقع بين الملف الشخصي والبيانات المرتبطة لتوفير سلوك مخصّص عند تغيير البيانات. في هذه الحالة، يكون السلوك المخصّص هو استدعاء"محو"لتحميل صورة من عنوان 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. أعلى العنصر <ImageView>، أضِف العنصر <data> لربط البيانات واربطه بالفئة 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: إضافة صور بسيطة التحميل والخطأ

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

  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()، أزِل المهام. استخدِم عامل تشغيل المساواة المرجعية (===) في 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()، يُرجى إزالة قائمة المهام المهام وإضافة السطر الموضح أدناه. استيراد android.view.LayoutInflater عند الطلب.

    يجب أن تعرض طريقة onCreateViewHolder() عرض MarsPropertyViewHolder جديدًا تم إنشاؤه عن طريق تضخيم GridViewItemBinding واستخدام LayoutInflater من سياق ViewGroup الرئيسي.
   return MarsPropertyViewHolder(GridViewItemBinding.inflate(
      LayoutInflater.from(parent.context)))
  1. في طريقة onBindViewHolder()، أزِل المهام المهام وأضِف الأسطر المعروضة أدناه. وعليك هنا استدعاء 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 صورة. أثناء التمرير لرؤية صور جديدة، يعرض التطبيق رمز تقدم التحميل قبل عرض الصورة نفسها. في حال تفعيل "وضع الطيران"، تظهر الصور التي لم يتم تحميلها بعد كرموز تشير إلى أعطال.

يعرض تطبيق Mars RealEstate رمز الصورة المعطلة عندما لا يمكن جلب الصورة. ولكن في حالة عدم وجود شبكة، يعرض التطبيق شاشة فارغة.

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

الخطوة 1: إضافة الحالة إلى نموذج الملف الشخصي

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

  1. فتح overview/OverviewViewModel.kt في أعلى الملف (بعد عمليات الاستيراد، وقبل تعريف الفئة)، أضِف enum لتمثيل جميع الحالات المتاحة:
enum class MarsApiStatus { LOADING, ERROR, DONE }
  1. أعد تسمية تعريفات البيانات المباشرة الداخلية والخارجية في _response على مستوى OverviewViewModel إلى _status. نظرًا لأنك أضفت دعمًا لـ _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 غير مرئي.

  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 على مرئي، خصِّص له حركة التحميل. هذه هي الصورة المتحركة نفسها القابلة للسحب التي استخدمتها في ميزة "الكتابة بالتمرير" في المهمة السابقة. استيراد android.view.View عند الطلب.
when (status) {
   MarsApiStatus.LOADING -> {
      statusImageView.visibility = View.VISIBLE
      statusImageView.setImageResource(R.drawable.loading_animation)
   }
}
  1. أضِف حالة لحالة الخطأ، وهي MarsApiStatus.ERROR. على غرار الإجراء الذي اتخذته في حالة LOADING، اضبط الحالة ImageView على "مرئية" وأعِد استخدام خطأ الاتصال القابل للرسم.
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 Studio: Mars RealEstateNetwork

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

دورة Udacity:

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

غير ذلك:

يسرد هذا القسم المهام الدراسية المحتملة للطلاب الذين يعملون من خلال هذا الدرس التطبيقي حول الترميز في إطار دورة تدريبية يُديرها معلِّم. يجب أن ينفِّذ المعلّم ما يلي:

  • يمكنك تخصيص واجب منزلي إذا لزم الأمر.
  • التواصل مع الطلاب بشأن كيفية إرسال الواجبات المنزلية
  • وضع درجات للواجبات المنزلية.

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

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

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

السؤال 1

ما طريقة الكتابة بالتمرير التي تستخدمها للإشارة إلى علامة 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 الفلترة وعرض التفاصيل باستخدام بيانات الإنترنت

وللحصول على روابط إلى دروس تطبيقية أخرى حول الترميز في هذه الدورة التدريبية، يُرجى الاطّلاع على الصفحة المقصودة لتطبيق الدروس التطبيقية حول الترميز Kotlin Fundamentals.