Android Kotlin Fundamentals 08.2: טעינה והצגה של תמונות מהאינטרנט

Lab Lab זה הוא חלק מקורס Android Kotlin Fundamentals. כדי להפיק את המקסימום מהקורס הזה, יש לפעול ברצף לפי קודי שיעור ה-Lab. כל שיעורי Lab של הקורסים מופיעים בדף הנחיתה של Lab Kotlin Fundamentals ל-Android Lab.

מבוא

במעבדה הקודמת, למדתם איך לקבל נתונים משירות אינטרנט ולנתח את התגובה לאובייקט נתונים. במעבדה זו, אתם בונים את הידע הזה כדי לטעון ולהציג תמונות מכתובת URL באינטרנט. כמו כן, אפשר לחזור על אופן היצירה של RecyclerView ולהשתמש בו כדי להציג רשת של תמונות בדף הסקירה הכללית.

מה כבר צריך לדעת

  • איך ליצור קטעי קוד ולהשתמש בהם.
  • כיצד להשתמש ברכיבי ארכיטקטורה, כולל מודלים של תצוגה, מפעלי תצוגה, טרנספורמציות ו-LiveData.
  • איך לאחזר קובץ JSON משירות אינטרנט של REST ולנתח את הנתונים האלה לאובייקטים של Kotlin באמצעות הספריות Retrofit ו-Moshi.
  • איך ליצור פריסת רשת עם RecyclerView.
  • איך Adapter, ViewHolder וDiffUtil פועלים.

מה תלמדו

  • איך להשתמש בספרייה של החלקה כדי לטעון ולהציג תמונה מכתובת URL.
  • איך להשתמש בRecyclerView ובמתאם רשת כדי להציג רשת תמונות.
  • כיצד לטפל בשגיאות פוטנציאליות במהלך הורדה והצגה של תמונות.

מה צריך לעשות

  • יש לשנות את אפליקציית MarsRealEstate כדי לקבל את כתובת ה-URL של התמונה מנתוני הנכס Mars, ולהשתמש בהחלקה כדי לטעון ולהציג את התמונה הזו.
  • הוספת אנימציה של טעינה וסמל שגיאה לאפליקציה.
  • אפשר להשתמש ב-RecyclerView כדי להציג רשת של תמונות מאדים.
  • הוספה של סטטוס טיפול ושגיאות ל-RecyclerView.

ב-Codelab הזה (וב-codelabs קשורים) אתם עובדים עם אפליקציה שנקראת MarsRealEstate, שמציגה נכסים למכירה במאדים. האפליקציה מתחברת לשרת אינטרנט כדי לאחזר ולהציג נתוני נכסים, כולל פרטים כמו המחיר והאם הנכס זמין למכירה או להשכרה. התמונות שמייצגות כל נכס הן תמונות מהחיים במאדים שצולמו על ידי רכבי הירח במאדים.

גרסת האפליקציה של גרסת ה-codelab הזו ממלאת את דף הסקירה הכללית, שבו מוצגות רשת תמונות. התמונות הן חלק מנתוני הנכס שהאפליקציה שלך מקבלת משירות הנדל"ן באינטרנט של Mars. האפליקציה תשתמש בספרייה בהחלקה כדי להציג ולהציג את התמונות, וב-RecyclerView כדי ליצור את פריסת הרשת של התמונות. האפליקציה תטפל גם בשגיאות רשת באופן חינני.

הצגת תמונה מכתובת אתר עשויה להישמע פשוטה, אבל יש קצת הנדסה טובה כדי שהיא תעבוד כמו שצריך. צריך להוריד את התמונה, לאחסן אותה ולפענח אותה בפורמט הדחוס שלה, כדי להשתמש בה בפורמט Android. התמונה צריכה להיות במטמון למטמון בזיכרון, למטמון מבוסס אחסון או לשניהם. כל זה צריך לקרות בשרשורים שבעדיפות נמוכה כדי שממשק המשתמש יישאר רספונסיבי. כמו כן, כדי לשפר את הביצועים ברשת ובמעבד, מומלץ לאחזר ולפענח יותר מתמונה אחת בבת אחת. למידה איך לטעון תמונות באופן אפקטיבי מהרשת יכולה להיות קוד Lab בלבד.

למרבה המזל, תוכלו להשתמש בספרייה שפותחה על ידי הקהילה בשם החלקה כדי להוריד את הנתונים, לאחסן במאגר את הנתונים שלהם, לפענח אותם ולאחסן אותם במטמון. בהחלקה אתם מקבלים הרבה פחות עבודה מאשר אם צריך לעשות את כל זה מאפס.

המטרה בהחלקה היא:

  • כתובת ה-URL של התמונה שרוצים לטעון ולהציג.
  • אובייקט ImageView כדי להציג את התמונה הזו.

במשימה הזו תלמדו איך להשתמש בהחלקה כדי להציג תמונה יחידה משירות הנדל"ן באינטרנט. אתם מציגים את התמונה שמייצגת את הנכס הראשון של מאדים ברשימת הנכסים ששירות האינטרנט מחזיר. לפניכם צילומי המסך שלפני ואחרי:

שלב 1: הוספת תלות בהחלקה

  1. פותחים את אפליקציית MarsRealEstate ממעבדת הקוד האחרונה. (אם אין לך את האפליקציה, אפשר להוריד את MarsRealEstateNetwork).
  2. מפעילים את האפליקציה כדי לראות מה היא עושה. (הוא מציג פרטי טקסט של נכס שזמין באופן היפותטי למאדים).
  3. פותחים את build.gradle (מודול: אפליקציה).
  4. בקטע dependencies, מוסיפים את השורה הזו לספריית ההחלקה:
implementation "com.github.bumptech.glide:glide:$version_glide"


שימו לב שמספר הגרסה כבר מוגדר בנפרד בקובץ Gradle של הפרויקט.

  1. לוחצים על Sync Now (ביצוע סנכרון) כדי לבנות מחדש את הפרויקט בהתאם לתלות החדשה.

שלב 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 של התמונה בנכס Mars הראשון. כל מה שעשית עד עכשיו הוגדר את מודל התצוגה ואת הנתונים הפעילים עבור כתובת ה-URL הזו.

שלב 3: יוצרים מתאם מחייב ומתקשרים להחלקה

עכשיו יש לכם כתובת URL של תמונה להצגה, והגיע הזמן להתחיל לעבוד עם התכונה 'החלקה' כדי לטעון את התמונה. בשלב זה, צריך להשתמש במתאם איגוד כדי להעביר את כתובת ה-URL ממאפיין XML שמשויך ל-ImageView, ולהשתמש בהחלקה כדי לטעון את התמונה. מתאמים לאיגוד הם שיטות של תוספים שפועלות בין תצוגה מפורטת לבין נתוני איגוד כדי לספק התנהגות מותאמת אישית כאשר הנתונים משתנים. במקרה הזה, ההתנהגות המותאמת אישית היא לבצע קריאה ב-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 מספריית הליבה של KTX ל-Android, כך שנראה פשוט כחלק מהמחלקה 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 בהמשך ב-Codelab. אתם משתמשים בו באופן זמני כדי להציג רק את התמונה היחידה.
  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: מוסיפים תמונות פשוטות של שגיאות וטעינה

החלקה יכולה לשפר את חוויית המשתמש על ידי הצגת תמונה placeholder בזמן טעינת התמונה ותמונת שגיאה אם הטעינה נכשלה, למשל אם התמונה חסרה או פגומה. בשלב זה, מוסיפים את הפונקציונליות הזו למתאם המחייב ולפריסה.

  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 כשמתבקשים.

    הקוד הזה מגדיר את תמונת ה-placeholder לטעינה, במהלך טעינה (את 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. מריצים את האפליקציה. בהתאם למהירות החיבור לרשת, ייתכן שבזמן הטעינה תוצג תמונת הטעינה בזמן שמתבצעת הורדה או הצגה של תמונת הנכס. עם זאת, לא תראו את סמל התמונה הפגומה, גם אם תכבו את הרשת – תפתרו את הבעיה בחלק האחרון של הקוד.

האפליקציה טוענת עכשיו את פרטי הנכס מהאינטרנט. על סמך נתונים מהפריט הראשון מסוג 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 עם הפרמטרים של ה-constructor שמוצגים בהמשך. הכיתה PhotoGridAdapter מרחיבה את ListAdapter. ה-constructor שלו צריך את סוג הפריט, בעל התצוגה וההטמעה של 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 תמונות. בזמן הגלילה כדי לראות תמונות חדשות, האפליקציה מציגה את סמל התקדמות הטעינה לפני הצגת התמונה עצמה. אם מפעילים את מצב טיסה, תמונות שעדיין לא נטענו יופיעו כסמלים של תמונות שבורות.

אפליקציית MarsRealEstate מציגה את סמל התמונה הפגומה כשאי אפשר לאחזר את התמונה. אבל כשאין רשת, האפליקציה מציגה מסך ריק.

זו חוויית משתמש מעולה. במשימה הזו אתם מוסיפים טיפול בסיסי לשגיאות, כדי לתת למשתמש מושג טוב יותר מה קורה. אם האינטרנט לא יהיה זמין, סמל השגיאה יוצג באפליקציה. בזמן שהאפליקציה מאחזרת את הרשימה MarsProperty, האפליקציה תציג את האנימציה של הטעינה.

שלב 1: הוספת סטטוס למודל התצוגה

כדי להתחיל, יוצרים LiveData במודל התצוגה, כדי לייצג את הסטטוס של בקשת האינטרנט. יש שלושה מצבים שכדאי להביא בחשבון – טעינה, הצלחה וכישלון. מצב הטעינה מתרחש בזמן ההמתנה לנתונים בשיחה ב-await().

  1. פתיחת overview/OverviewViewModel.kt בחלק העליון של הקובץ (לאחר הייבוא, לפני הגדרת הכיתה), מוסיפים enum כדי לייצג את כל הסטטוסים הזמינים:
enum class MarsApiStatus { LOADING, ERROR, DONE }
  1. שינוי השם של ההגדרה הפעילה ושל הגדרת הנתונים בזמן אמת של _response בכל השיעורים ב-OverviewViewModel ל-_status. מאחר שהוספת תמיכה ב-_properties LiveData מוקדם יותר ב-codelab הזה, לא נעשה שימוש בתגובה המלאה לשירות אינטרנט. יש צורך בערך 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: מוסיפים מתאם מחייב לסטטוס PhotoView

עכשיו יש לכם סטטוס במודל התצוגה, אבל הוא פשוט קבוצת מדינות. איך מציגים אותו באפליקציה עצמה? בשלב זה משתמשים ב-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: MarsRealEstateGrid

  • כדי לפשט את תהליך ניהול התמונות, אפשר להוריד את האפליקציה, להשתמש בספריית ההחלקה כדי להוריד תמונות, לאחסן אותן, לפענח אותן ולאחסן במטמון את האפליקציה.
  • ב-החלקה יש צורך בשני דברים כדי לטעון תמונה מהאינטרנט: כתובת URL של תמונה, ואובייקט ImageView כדי להוסיף את התמונה. כדי לציין את האפשרויות האלה, צריך להשתמש בשיטות load() ו-into() בהחלקה.
  • מתאמים לחייב הם שיטות תוסף המוקפות בין תצוגה מפורטת לבין נתוני איגוד של תצוגה מפורטת זו. מתאמים לאיגוד מספקים התנהגות מותאמת אישית כאשר הנתונים משתנים, למשל, כדי להתקשר להחלקה כדי לטעון תמונה מכתובת URL אל ImageView.
  • מתאמים לכריכה הם שיטות הערות עם ההערה @BindingAdapter.
  • כדי להוסיף אפשרויות לבקשת ההחלקה, צריך להשתמש בשיטה apply(). לדוגמה, אפשר להשתמש ב-apply() עם placeholder() כדי לציין פריט גרפי שניתן לטעינה, ולהשתמש ב-apply() עם error() כדי לציין שגיאה שניתן לשרטט.
  • כדי ליצור רשת תמונות, יש להשתמש ב-RecyclerView עם GridLayoutManager.
  • כדי לעדכן את רשימת הנכסים כאשר היא משתנה, יש להשתמש במתאם מחייב בין RecyclerView לבין הפריסה.

קורס אוניברסיטה:

התיעוד של מפתח Android:

אחר:

בקטע הזה מפורטות מטלות שיעורי בית אפשריות לתלמידים שעובדים עם קוד Lab הזה, במסגרת קורס בהדרכת מורה. למורה יש אפשרות לבצע את הפעולות הבאות:

  • אם צריך, מקצים שיעורי בית.
  • ספרו לתלמידים איך מגישים מטלות בשיעורי בית.
  • לתת ציונים למטלות שיעורי הבית.

המורים יכולים להשתמש בהצעות האלה כמה שפחות, ומומלץ להקצות להן כל שיעורי בית שדעתם מתאימה להם.

אם אתם עובדים בעצמכם על שיעור הקוד הזה, אתם מוזמנים להשתמש במטלות שיעורי הבית האלה כדי לבחון את הידע שלכם.

מענה על השאלות הבאות

שאלה 1

באיזו שיטת החלקה השתמשת כדי לציין את ImageView שיכלול את התמונה שנטענה?

into()

with()

imageview()

apply()

שאלה 2

איך אתם מציינים תמונת placeholder שתוצג בעת הטעינה?

הוספה יש להשתמש בשיטה into() עם אפשרות לשרטוט.

▬ להשתמש ב-RequestOptions() ולהתקשר לשיטת placeholder() עם שרטוט.

▬ להקצות את המאפיין Glide.placeholder למקום שניתן לשרטוט.

▬ להשתמש ב-RequestOptions() ולהתקשר לשיטת loadingImage() עם שרטוט.

שאלה 3

איך אפשר לציין ששיטה היא מתאם מחייב?

הוספה יש להתקשר אל השיטה setBindingAdapter() במכשיר LiveData.

הוספה יש להוסיף את השיטה לקובץ Kotlin בשם BindingAdapters.kt.

▬ להשתמש במאפיין android:adapter בפריסת XML.

הוספה של הערות באמצעות השיטה עם @BindingAdapter.

מעבר לשיעור הבא: 8.3 סינון ותצוגות מפורטות באמצעות נתוני אינטרנט

קישורים למעבדות אחרות של הקוד בקורס הזה זמינים בדף הנחיתה של Android Kotlin Fundamentals Codelabs.