هذا الدرس التطبيقي حول الترميز هو جزء من دورة "أساسيات 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
- افتح تطبيق MarsRealEstate من آخر تجربة عملية. (يمكنك تنزيل تطبيق MarsRealEstateNetwork من هنا إذا لم يكن مثبّتًا لديك).
- شغِّل التطبيق لمعرفة ما يمكنه فعله. (تعرض هذه السمة تفاصيل نصية عن عقار متوفّر افتراضيًا على كوكب المريخ).
- افتح ملف build.gradle (وحدة التطبيق).
- في القسم
dependencies، أضِف السطر التالي لمكتبة Glide:
implementation "com.github.bumptech.glide:glide:$version_glide"
يُرجى العِلم أنّه تم تحديد رقم الإصدار بشكل منفصل في ملف Gradle الخاص بالمشروع.
- انقر على المزامنة الآن لإعادة إنشاء المشروع باستخدام التبعية الجديدة.
الخطوة 2: تعديل نموذج العرض
بعد ذلك، عدِّل الفئة OverviewViewModel لتضمين بيانات مباشرة لموقع واحد على كوكب المريخ.
- فتح "
overview/OverviewViewModel.kt" أسفلLiveDataالخاص بـ_responseمباشرةً، أضِف بيانات البث المباشر الداخلية (القابلة للتغيير) والخارجية (غير القابلة للتغيير) لكائنMarsPropertyواحد.
استيراد الصفMarsProperty(com.example.android.marsrealestate.network.MarsProperty) عند الطلب
private val _property = MutableLiveData<MarsProperty>()
val property: LiveData<MarsProperty>
get() = _property- في طريقة
getMarsRealEstateProperties()، ابحث عن السطر داخل الحظرtry/catch {}الذي يضبط_response.valueعلى عدد المواقع. أضِف الاختبار الموضّح أدناه. في حال توفّر عناصرMarsProperty، يضبط هذا الاختبار قيمة_propertyLiveDataعلى السمة الأولى في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}"
}- افتح ملف
res/layout/fragment_overview.xml. في العنصر<TextView>، غيِّرandroid:textللربط بالمكوّنimgSrcUrlمنpropertyLiveData:
android:text="@{viewModel.property.imgSrcUrl}"- شغِّل التطبيق. لا يعرض
TextViewسوى عنوان URL للصورة في الموقع الأول على كوكب المريخ. كل ما فعلته حتى الآن هو إعداد نموذج العرض والبيانات النشطة لعنوان URL هذا.

الخطوة 3: إنشاء أداة ربط البيانات واستدعاء Glide
الآن لديك عنوان URL لصورة تريد عرضها، وحان الوقت لبدء استخدام Glide لتحميل هذه الصورة. في هذه الخطوة، ستستخدم برنامج ربط للحصول على عنوان URL من سمة XML مرتبطة بعنصر ImageView، وستستخدم Glide لتحميل الصورة. محوّلات الربط هي طرق إضافية تقع بين طريقة العرض والبيانات المرتبطة لتوفير سلوك مخصّص عند تغيير البيانات. في هذه الحالة، يكون السلوك المخصّص هو استدعاء Glide لتحميل صورة من عنوان URL إلى ImageView.
- فتح "
BindingAdapters.kt" سيحتوي هذا الملف على أدوات ربط البيانات التي تستخدمها في جميع أنحاء التطبيق. - أنشئ الدالة
bindImage()التي تأخذImageViewوStringكمعلَمات. أضِف التعليق التوضيحي@BindingAdapterإلى الدالة. تخبر التعليق التوضيحي@BindingAdapterميزة ربط البيانات بأنّك تريد تنفيذ أداة ربط البيانات هذه عندما يحتوي عنصر XML على السمةimageUrl.
استيرادandroidx.databinding.BindingAdapterوandroid.widget.ImageViewعند الطلب
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
}- داخل الدالة
bindImage()، أضِف مجموعةlet {}للوسيطةimgUrl:
imgUrl?.let {
}- داخل الحظر
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()- لا يزال داخل
let {}، استدعِGlide.with()لتحميل الصورة من العنصرUriإلىImageView. استورِدcom.bumptech.glide.Glideعند الطلب.
Glide.with(imgView.context)
.load(imgUri)
.into(imgView)الخطوة 4: تعديل التنسيق والقصاصات
على الرغم من أنّ Glide حمّل الصورة، لا يوجد ما يمكن رؤيته حتى الآن. الخطوة التالية هي تعديل التنسيق والأجزاء باستخدام ImageView لعرض الصورة.
- فتح "
res/layout/gridview_item.xml" هذا هو ملف مصدر التنسيق الذي ستستخدمه لكل عنصر فيRecyclerViewلاحقًا في الدرس العملي. يمكنك استخدامها مؤقتًا هنا لعرض الصورة الفردية فقط. - أضِف عنصر
<data>لربط البيانات فوق عنصر<ImageView>، واربطه بفئةOverviewViewModel:
<data>
<variable
name="viewModel"
type="com.example.android.marsrealestate.overview.OverviewViewModel" />
</data>- أضِف السمة
app:imageUrlإلى العنصرImageViewلاستخدام أداة ربط تحميل الصور الجديدة:
app:imageUrl="@{viewModel.property.imgSrcUrl}"- فتح "
overview/OverviewFragment.kt" في طريقةonCreateView()، علِّق على السطر الذي يوسّع الفئةFragmentOverviewBindingويعيّنها لمتغيّر الربط. هذا التغيير مؤقّت وسيعود حسابك إلى الوضع السابق لاحقًا.
//val binding = FragmentOverviewBinding.inflate(inflater)- أضِف سطرًا لتضخيم فئة
GridViewItemBindingبدلاً من ذلك. استورِدcom.example.android.marsrealestate. databinding.GridViewItemBindingعند الطلب.
val binding = GridViewItemBinding.inflate(inflater)- شغِّل التطبيق. من المفترض أن تظهر لك الآن صورة من
MarsPropertyفي قائمة النتائج.
الخطوة 5: إضافة صور بسيطة للتحميل والأخطاء
يمكن أن يحسّن Glide تجربة المستخدم من خلال عرض صورة عنصر نائب أثناء تحميل الصورة وصورة خطأ في حال تعذّر التحميل، مثلاً إذا كانت الصورة غير متوفّرة أو تالفة. في هذه الخطوة، ستضيف هذه الوظيفة إلى أداة ربط البيانات وإلى التصميم.
- افتح
res/drawable/ic_broken_image.xml، ثم انقر على علامة التبويب معاينة على يسار الصفحة. بالنسبة إلى صورة الخطأ، يمكنك استخدام رمز الصورة المعطّلة المتوفّر في مكتبة الرموز المضمّنة. يستخدم هذا الرسم المتّجه القابل للرسم السمةandroid:tintلتلوين الرمز باللون الرمادي.

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

- الرجوع إلى ملف
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)
}
}
- شغِّل التطبيق. استنادًا إلى سرعة اتصالك بالشبكة، قد تظهر لك صورة التحميل لفترة وجيزة أثناء تنزيل Glide لصورة المكان وعرضها. ولكن لن يظهر لك رمز الصورة المعطّلة بعد، حتى إذا أوقفت شبكتك، لأنّك ستصلح ذلك في الجزء الأخير من الدرس العملي.
يحمّل تطبيقك الآن معلومات الموقع من الإنترنت. باستخدام البيانات من عنصر القائمة الأول MarsProperty، أنشأت السمة LiveData في نموذج العرض، واستخدمت عنوان URL للصورة من بيانات هذه السمة لملء ImageView. ولكن الهدف هو أن يعرض تطبيقك شبكة من الصور، لذا عليك استخدام RecyclerView مع GridLayoutManager.
الخطوة 1: تعديل نموذج العرض
يحتوي نموذج العرض حاليًا على _property LiveData يتضمّن عنصر MarsProperty واحدًا، وهو العنصر الأول في قائمة الردود من خدمة الويب. في هذه الخطوة، يمكنك تغيير LiveData لتضمين القائمة الكاملة لعناصر MarsProperty.
- فتح "
overview/OverviewViewModel.kt" - غيِّر المتغيّر الخاص
_propertyإلى_properties. غيِّر النوع إلى قائمة بعناصرMarsProperty.
private val _properties = MutableLiveData<List<MarsProperty>>()- استبدِل البيانات المباشرة الخارجية
propertyبـproperties. أضِف القائمة إلى النوعLiveDataهنا أيضًا:
val properties: LiveData<List<MarsProperty>>
get() = _properties- انتقِل إلى طريقة
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: تعديل التصاميم والرموز
الخطوة التالية هي تغيير تصميم التطبيق وأجزائه لاستخدام طريقة عرض قابلة لإعادة الاستخدام وتصميم شبكي، بدلاً من طريقة عرض صورة واحدة.
- فتح "
res/layout/gridview_item.xml" غيِّر ربط البيانات منOverviewViewModelإلىMarsProperty، وأعِد تسمية المتغيّر إلى"property".
<variable
name="property"
type="com.example.android.marsrealestate.network.MarsProperty" />- في
<ImageView>، غيِّر السمةapp:imageUrlللإشارة إلى عنوان URL للصورة في العنصرMarsProperty:
app:imageUrl="@{property.imgSrcUrl}"- فتح "
overview/OverviewFragment.kt" فيonCreateview()، أزِل التعليق من السطر الذي يوسّعFragmentOverviewBinding. احذف السطر الذي يوسّعGridViewBindingأو علِّق عليه. تؤدي هذه التغييرات إلى التراجع عن التغييرات المؤقتة التي أجريتها في المهمة الأخيرة.
val binding = FragmentOverviewBinding.inflate(inflater)
// val binding = GridViewItemBinding.inflate(inflater)- فتح "
res/layout/fragment_overview.xml" احذف عنصر<TextView>بالكامل. - أضِف عنصر
<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.
- فتح "
overview/PhotoGridAdapter.kt" - أنشئ الفئة
PhotoGridAdapterباستخدام مَعلمات الدالة الإنشائية الموضّحة أدناه. يمتد الصفPhotoGridAdapterإلىListAdapter، الذي يحتاج منشئه إلى نوع عنصر القائمة وحاوية العرض وتنفيذDiffUtil.ItemCallback.
استورِد الصفَّينandroidx.recyclerview.widget.ListAdapterوcom.example.android.marsrealestate.network.MarsPropertyعند الطلب. في الخطوات التالية، ستنفّذ الأجزاء الأخرى الناقصة من أداة الإنشاء هذه التي تتسبّب في حدوث أخطاء.
class PhotoGridAdapter : ListAdapter<MarsProperty,
PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
}- انقر في أي مكان في الفئة
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")
}- في نهاية تعريف الفئة
PhotoGridAdapter، بعد الطرق التي أضفتها للتو، أضِف تعريف عنصر مصاحب لـDiffCallback، كما هو موضّح أدناه.
انقر على "استيراد"androidx.recyclerview.widget.DiffUtilعندما يُطلب منك ذلك.
يمتد العنصرDiffCallbackإلىDiffUtil.ItemCallbackمع نوع العنصر الذي تريد مقارنته، وهوMarsProperty.
companion object DiffCallback : DiffUtil.ItemCallback<MarsProperty>() {
}- اضغط على
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") }- بالنسبة إلى طريقة
areItemsTheSame()، أزِل TODO. استخدِم عامل المساواة المرجعية في Kotlin (===)، الذي يعرض القيمةtrueإذا كانت مراجع العناصر لكل منoldItemوnewItemهي نفسها.
override fun areItemsTheSame(oldItem: MarsProperty,
newItem: MarsProperty): Boolean {
return oldItem === newItem
}- بالنسبة إلى
areContentsTheSame()، استخدِم عامل المساواة العادي على معرّفoldItemوnewItemفقط.
override fun areContentsTheSame(oldItem: MarsProperty,
newItem: MarsProperty): Boolean {
return oldItem.id == newItem.id
}- داخل الفئة
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) {
}- في
MarsPropertyViewHolder، أنشئ طريقةbind()تأخذ كائنMarsPropertyكوسيطة وتضبطbinding.propertyعلى هذا الكائن. استدعِ الدالةexecutePendingBindings()بعد ضبط السمة، ما يؤدي إلى تنفيذ التعديل على الفور.
fun bind(marsProperty: MarsProperty) {
binding.property = marsProperty
binding.executePendingBindings()
}- في
onCreateViewHolder()، أزِل TODO وأضِف السطر الموضّح أدناه. استورِدandroid.view.LayoutInflaterعند الطلب.
يجب أن تعرض الطريقةonCreateViewHolder()MarsPropertyViewHolderجديدًا تم إنشاؤه من خلال توسيعGridViewItemBindingواستخدامLayoutInflaterمن سياقViewGroupالأصل.
return MarsPropertyViewHolder(GridViewItemBinding.inflate(
LayoutInflater.from(parent.context)))- في طريقة
onBindViewHolder()، أزِل TODO وأضِف الأسطر الموضّحة أدناه. هنا، يمكنك استدعاءgetItem()للحصول على عنصرMarsPropertyالمرتبط بموضعRecyclerViewالحالي، ثم تمرير هذا الموقع إلى طريقةbind()فيMarsPropertyViewHolder.
val marsProperty = getItem(position)
holder.bind(marsProperty)الخطوة 4: إضافة أداة ربط البيانات وربط الأجزاء
أخيرًا، استخدِم BindingAdapter لتهيئة PhotoGridAdapter بقائمة كائنات MarsProperty. يؤدي استخدام BindingAdapter لضبط بيانات RecyclerView إلى جعل ربط البيانات يراقب LiveData تلقائيًا لقائمة عناصر MarsProperty. بعد ذلك، يتم استدعاء أداة الربط تلقائيًا عند تغيير القائمة MarsProperty.
- فتح "
BindingAdapters.kt" - في نهاية الملف، أضِف طريقة
bindRecyclerView()تأخذRecyclerViewوقائمة بكائناتMarsPropertyكمعلمات. أضِف التعليق التوضيحي@BindingAdapterإلى هذه الطريقة.
استيرادandroidx.recyclerview.widget.RecyclerViewوcom.example.android.marsrealestate.network.MarsPropertyعند الطلب
@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView,
data: List<MarsProperty>?) {
}- داخل الدالة
bindRecyclerView()، حوِّلrecyclerView.adapterإلىPhotoGridAdapter، واستدعِadapter.submitList()باستخدام البيانات. يُعلم هذا الإجراءRecyclerViewعند توفّر قائمة جديدة.
استورِد com.example.android.marsrealestate.overview.PhotoGridAdapter عند الطلب.
val adapter = recyclerView.adapter as PhotoGridAdapter
adapter.submitList(data)- فتح "
res/layout/fragment_overview.xml" أضِف السمةapp:listDataإلى العنصرRecyclerViewواضبطها علىviewmodel.propertiesباستخدام ربط البيانات.
app:listData="@{viewModel.properties}"- فتح "
overview/OverviewFragment.kt" فيonCreateView()، قبل طلبsetHasOptionsMenu()مباشرةً، عليك إعداد محوّلRecyclerViewفيbinding.photosGridإلى عنصرPhotoGridAdapterجديد.
binding.photosGrid.adapter = PhotoGridAdapter()- شغِّل التطبيق. من المفترض أن تظهر لك شبكة من صور
MarsProperty. أثناء التنقّل للاطّلاع على صور جديدة، يعرض التطبيق رمز تقدّم التحميل قبل عرض الصورة نفسها. في حال تفعيل وضع الطائرة، ستظهر الصور التي لم يتم تحميلها بعد على شكل رموز صور تالفة.

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

هذا ليس خيارًا جيدًا لتجربة المستخدم. في هذه المهمة، ستضيف معالجة أساسية للأخطاء، وذلك لمنح المستخدم فكرة أفضل عمّا يحدث. إذا لم يكن الإنترنت متاحًا، سيعرض التطبيق رمز خطأ الاتصال. أثناء جلب التطبيق لقائمة MarsProperty، سيعرض صورة متحركة للتحميل.
الخطوة 1: إضافة حالة إلى نموذج العرض
للبدء، يمكنك إنشاء LiveData في نموذج العرض لتمثيل حالة طلب الويب. هناك ثلاث حالات يجب أخذها في الاعتبار: التحميل والنجاح والفشل. تحدث حالة التحميل أثناء انتظار البيانات في طلب await().
- فتح "
overview/OverviewViewModel.kt" في أعلى الملف (بعد عمليات الاستيراد، وقبل تعريف الفئة)، أضِفenumلتمثيل جميع الحالات المتاحة:
enum class MarsApiStatus { LOADING, ERROR, DONE }- أعِد تسمية تعريفات البيانات المباشرة الداخلية والخارجية في جميع أنحاء فئة
OverviewViewModelإلى_status._responseبما أنّك أضفت دعمًا لـ_propertiesLiveDataفي وقت سابق من هذا الدرس العملي، لم يتم استخدام استجابة خدمة الويب الكاملة. أنت بحاجة إلىLiveDataهنا لتتبُّع الحالة الحالية، لذا يمكنك إعادة تسمية المتغيّرات الحالية فقط.
غيِّر أيضًا الأنواع من String إلى MarsApiStatus..
private val _status = MutableLiveData<MarsApiStatus>()
val status: LiveData<MarsApiStatus>
get() = _status- انتقِل للأسفل إلى طريقة الدفع
getMarsRealEstateProperties()وعدِّل_responseإلى_statusهنا أيضًا. غيِّر السلسلة"Success"إلى الحالةMarsApiStatus.DONE، والسلسلة"Failure"إلىMarsApiStatus.ERROR. - أضِف حالة
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
}- بعد حالة الخطأ في الحظر
catch {}، اضبط_propertiesLiveDataعلى قائمة فارغة. سيؤدي ذلك إلى محوRecyclerView.
} catch (e: Exception) {
_status.value = MarsApiStatus.ERROR
_properties.value = ArrayList()
}الخطوة 2: إضافة أداة ربط لـ ImageView الخاص بالحالة
الآن لديك حالة في نموذج العرض، ولكنّها مجرد مجموعة من الحالات. كيف يمكن إظهارها في التطبيق نفسه؟ في هذه الخطوة، ستستخدم ImageView مرتبطًا بربط البيانات لعرض رموز لحالتَي التحميل والخطأ. عندما يكون التطبيق في حالة التحميل أو حالة الخطأ، يجب أن يظهر ImageView. عند انتهاء تحميل التطبيق، يجب أن يكون الرمز ImageView غير مرئي.
- فتح "
BindingAdapters.kt" أضِف أداة ربط جديدة باسمbindStatus()تأخذ قيمتَيImageViewوMarsApiStatusكمعلَمات. استورِدcom.example.android.marsrealestate.overview.MarsApiStatusعند الطلب.
@BindingAdapter("marsApiStatus")
fun bindStatus(statusImageView: ImageView,
status: MarsApiStatus?) {
}- أضِف
when {}داخل طريقةbindStatus()للتبديل بين الحالات المختلفة.
when (status) {
}- داخل
when {}، أضِف حالة التحميل (MarsApiStatus.LOADING). بالنسبة إلى هذه الحالة، اضبطImageViewعلى "مرئي"، وعيِّن له الصورة المتحركة الخاصة بالتحميل. هذا هو رسم الحركة نفسه الذي استخدمته في Glide في المهمة السابقة. استورِدandroid.view.Viewعند الطلب.
when (status) {
MarsApiStatus.LOADING -> {
statusImageView.visibility = View.VISIBLE
statusImageView.setImageResource(R.drawable.loading_animation)
}
}- أضِف حالة الخطأ، وهي
MarsApiStatus.ERROR. على غرار ما فعلته للحالةLOADING، اضبط الحالةImageViewعلى مرئية وأعِد استخدام العنصر القابل للرسم connection-error.
MarsApiStatus.ERROR -> {
statusImageView.visibility = View.VISIBLE
statusImageView.setImageResource(R.drawable.ic_connection_error)
}- أضِف حالة "تم"، وهي
MarsApiStatus.DONE. في هذه الحالة، لديك استجابة ناجحة، لذا أوقِف إذن الوصول إلى الحالةImageViewلإخفائها.
MarsApiStatus.DONE -> {
statusImageView.visibility = View.GONE
}الخطوة 3: إضافة ImageView الخاص بالحالة إلى التصميم
- فتح "
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}" />- فعِّل وضع الطيران في المحاكي أو الجهاز لمحاكاة عدم توفّر اتصال بالشبكة. جمِّع التطبيق وشغِّله، ولاحظ ظهور صورة الخطأ:

- انقر على زر الرجوع لإغلاق التطبيق، وأوقِف وضع الطائرة. استخدِم شاشة "التطبيقات الحديثة" للرجوع إلى التطبيق. استنادًا إلى سرعة اتصال الشبكة، قد يظهر مؤشر تحميل لفترة قصيرة جدًا عندما يطلب التطبيق من خدمة الويب تحميل الصور.
مشروع "استوديو Android": MarsRealEstateGrid
- لتسهيل عملية إدارة الصور، استخدِم مكتبة Glide لتنزيل الصور وتخزينها مؤقتًا وفك ترميزها وتخزينها في ذاكرة التخزين المؤقت في تطبيقك.
- يحتاج Glide إلى عنصرَين لتحميل صورة من الإنترنت: عنوان URL للصورة وعنصر
ImageViewلوضع الصورة فيه. لتحديد هذه الخيارات، استخدِم الطريقتَينload()وinto()مع Glide. - محوّلات الربط هي طرق إضافية تقع بين طريقة العرض والبيانات المرتبطة بطريقة العرض هذه. توفّر أدوات ربط البيانات سلوكًا مخصّصًا عند تغيُّر البيانات، مثلاً، لاستدعاء Glide لتحميل صورة من عنوان URL إلى
ImageView. - محوّلات الربط هي طرق إضافية يتمّ وضع التعليق التوضيحي
@BindingAdapterعليها. - لإضافة خيارات إلى طلب Glide، استخدِم طريقة
apply(). على سبيل المثال، استخدِمapply()معplaceholder()لتحديد عنصر قابل للرسم خاص بالتحميل، واستخدِمapply()معerror()لتحديد عنصر قابل للرسم خاص بالخطأ. - لإنشاء شبكة من الصور، استخدِم
RecyclerViewمعGridLayoutManager. - لتعديل قائمة المواقع عند تغييرها، استخدِم أداة ربط بين
RecyclerViewوالتنسيق.
دورة Udacity التدريبية:
مستندات مطوّري تطبيقات Android:
- نظرة عامة على ViewModel
- نظرة عامة على LiveData
- الروتينات المشتركة، المستندات الرسمية
- برامج ربط البيانات
غير ذلك:
يسرد هذا القسم مهامًا منزلية محتملة للطلاب الذين يعملون على هذا الدرس التطبيقي العملي كجزء من دورة تدريبية يقودها مدرّب. على المعلّم تنفيذ ما يلي:
- حدِّد واجبًا منزليًا إذا لزم الأمر.
- توضيح كيفية إرسال الواجبات المنزلية للطلاب
- وضع درجات للواجبات المنزلية
يمكن للمدرّبين استخدام هذه الاقتراحات بالقدر الذي يريدونه، ويجب ألا يترددوا في تكليف الطلاب بأي واجبات منزلية أخرى يرونها مناسبة.
إذا كنت تعمل على هذا الدرس العملي بنفسك، يمكنك استخدام مهام الواجب المنزلي هذه لاختبار معلوماتك.
الإجابة عن هذه الأسئلة
السؤال 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 إلى الطريقة.
ابدأ الدرس التالي:
للحصول على روابط تؤدي إلى دروس تطبيقية أخرى في هذه الدورة التدريبية، اطّلِع على الصفحة المقصودة الخاصة بالدروس التطبيقية حول أساسيات Android Kotlin.