ה-codelab הזה הוא חלק מהקורס Android Kotlin Fundamentals. כדי להפיק את המרב מהקורס הזה, מומלץ לעבוד על ה-codelabs לפי הסדר. כל ה-codelab של הקורס מפורטים בדף הנחיתה של ה-codelab בנושא יסודות Kotlin ל-Android.
מבוא
ב-codelab הקודם למדתם איך לקבל נתונים משירות אינטרנט ולנתח את התגובה לאובייקט נתונים. ב-Codelab הזה תשתמשו בידע הזה כדי לטעון ולהציג תמונות מכתובת URL באינטרנט. בנוסף, נחזור אל אופן בניית RecyclerView ונשתמש בו כדי להציג רשת של תמונות בדף הסקירה הכללית.
מה שכדאי לדעת
- איך יוצרים קטעים ומשתמשים בהם.
- איך משתמשים ברכיבי ארכיטקטורה, כולל מודלים של תצוגה, מפעלים של מודלים של תצוגה, טרנספורמציות ו-
LiveData. - איך לאחזר JSON משירות אינטרנט מסוג REST ולנתח את הנתונים לאובייקטים של Kotlin באמצעות הספריות Retrofit ו-Moshi.
- איך יוצרים פריסת רשת באמצעות התג
RecyclerView. - איך פועלים
Adapter,ViewHolderו-DiffUtil
מה תלמדו
- איך משתמשים בספריית Glide כדי לטעון ולהציג תמונה מכתובת URL באינטרנט.
- איך משתמשים ב-
RecyclerViewובמתאם רשת כדי להציג רשת של תמונות. - איך לטפל בשגיאות פוטנציאליות במהלך ההורדה וההצגה של התמונות.
מה עושים
- משנים את האפליקציה MarsRealEstate כדי לקבל את כתובת ה-URL של התמונה מנתוני הנכס במאדים, ומשתמשים ב-Glide כדי לטעון ולהציג את התמונה.
- הוספת אנימציה של טעינה וסמל שגיאה לאפליקציה.
- משתמשים ב-
RecyclerViewכדי להציג רשת של תמונות של נכסים במאדים. - מוסיפים סטטוס וטיפול בשגיאות ל-
RecyclerView.
במדריך הזה (ובמדריכים קשורים), תעבדו עם אפליקציה בשם MarsRealEstate, שמציגה נכסים למכירה במאדים. האפליקציה מתחברת לשרת באינטרנט כדי לאחזר ולהציג נתוני נכסים, כולל פרטים כמו המחיר והאם הנכס זמין למכירה או להשכרה. התמונות שמייצגות כל נכס הן תמונות אמיתיות ממאדים שצולמו על ידי הרוברים של נאס"א על מאדים.

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


שלב 1: הוספת תלות ב-Glide
- פותחים את אפליקציית MarsRealEstate מה-codelab האחרון. (אם האפליקציה לא מותקנת במכשיר, אפשר להוריד את MarsRealEstateNetwork מכאן).
- מריצים את האפליקציה כדי לראות מה היא עושה. (מוצגים פרטי טקסט של נכס שזמין באופן היפותטי במאדים).
- פותחים את build.gradle (Module: app).
- בקטע
dependencies, מוסיפים את השורה הזו לספריית Glide:
implementation "com.github.bumptech.glide:glide:$version_glide"
שימו לב שמספר הגרסה כבר מוגדר בנפרד בקובץ הפרויקט של Gradle.
- לוחצים על סנכרון עכשיו כדי לבנות מחדש את הפרויקט עם התלות החדשה.
שלב 2: עדכון מודל התצוגה
אחר כך מעדכנים את המחלקה OverviewViewModel כך שתכלול נתונים בזמן אמת עבור נכס יחיד של Mars.
- פתיחת
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: יוצרים מתאם של Binding וקוראים ל-Glide
עכשיו יש לכם את כתובת ה-URL של התמונה שאתם רוצים להציג, והגיע הזמן להתחיל לעבוד עם Glide כדי לטעון את התמונה הזו. בשלב הזה, משתמשים במתאם איגוד כדי לקחת את כתובת ה-URL ממאפיין XML שמשויך ל-ImageView, ומשתמשים ב-Glide כדי לטעון את התמונה. מתאמי קישור הם שיטות הרחבה שמוצבות בין תצוגה לבין נתונים מקושרים, כדי לספק התנהגות מותאמת אישית כשהנתונים משתנים. במקרה הזה, ההתנהגות המותאמת אישית היא קריאה ל-Glide כדי לטעון תמונה מכתובת URL לתוך ImageView.
- פתיחת
BindingAdapters.kt. הקובץ הזה יכיל את מתאמי הקישור שבהם משתמשים באפליקציה. - יוצרים פונקציה
bindImage()שמקבלתImageViewו-Stringכפרמטרים. מוסיפים הערה לפונקציה עם@BindingAdapter. ההערה@BindingAdapterמציינת ל-Data Binding שרוצים שהמתאם הזה יופעל כשפריט 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")ל-toUribuilder. השיטה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בהמשך ה-codelab. אתם משתמשים בו באופן זמני כדי להציג רק את התמונה הבודדת. - מעל האלמנט
<ImageView>, מוסיפים אלמנט<data>לקישור הנתונים ומקשרים אותו למחלקה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 יכולה לשפר את חוויית המשתמש על ידי הצגת תמונת placeholder בזמן שהתמונה נטענת, ותמונת שגיאה אם הטעינה נכשלת, למשל אם התמונה חסרה או פגומה. בשלב הזה מוסיפים את הפונקציונליות הזו למתאם הקישור ולפריסה.
- פותחים את
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כשמתבקשים.
הקוד הזה מגדיר את תמונת הטעינה של ה-placeholder לשימוש בזמן הטעינה (ה-drawableloading_animation). הקוד גם מגדיר תמונה לשימוש אם טעינת התמונה נכשלת (ה-drawablebroken_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)
}
}
- מריצים את האפליקציה. בהתאם למהירות החיבור לרשת, יכול להיות שתראו לזמן קצר את תמונת הטעינה בזמן שהמערכת מורידה ומציגה את תמונת הנכס. אבל עדיין לא תראו את סמל התמונה השבורה, גם אם תכבו את הרשת – תתקנו את זה בחלק האחרון של ה-codelab.
האפליקציה שלכם טוענת עכשיו את פרטי הנכס מהאינטרנט. באמצעות הנתונים מפריט הרשימה הראשון 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>הזה, שמשתמש בפריסתgrid_view_itemוב-GridLayoutManagerלפריט בודד:
<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עם פרמטרי הבנאי שמוצגים למטה. המחלקות מרחיבות את המחלקות , והבנאי שלהן צריך את סוג פריט הרשימה, את מחזיק התצוגה ואת ההטמעה של .PhotoGridAdapterListAdapterDiffUtil.ItemCallback
מייבאים את הכיתותandroidx.recyclerview.widget.ListAdapterו-com.example.android.marsrealestate.network.MarsPropertyכשמתבקשים לעשות זאת. בשלבים הבאים, מטמיעים את החלקים החסרים האחרים של ה-constructor הזה שגורמים לשגיאות.
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 }- משנים את השם של ההגדרות של הנתונים הפעילים הפנימיים והחיצוניים של
_responseבכל מקום בכיתהOverviewViewModelל-_status. מכיוון שהוספת תמיכה ב-_propertiesLiveDataמוקדם יותר ב-codelab הזה, התשובה המלאה של שירות האינטרנט לא הייתה בשימוש. כדי לעקוב אחרי הסטטוס הנוכחי, צריך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: הוספת מתאם של Binding עבור 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כגלוי ומשתמשים מחדש ב-drawable של שגיאת החיבור.
MarsApiStatus.ERROR -> {
statusImageView.visibility = View.VISIBLE
statusImageView.setImageResource(R.drawable.ic_connection_error)
}- מוסיפים תרחיש שימוש למצב 'בוצע', שהוא
MarsApiStatus.DONE. התשובה תקינה, לכן צריך להשבית את ההגדרה 'חשיפה' של הסטטוסImageViewכדי להסתיר אותו.
MarsApiStatus.DONE -> {
statusImageView.visibility = View.GONE
}שלב 3: הוספת תצוגת התמונה של הסטטוס לפריסה
- פתיחת
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 Studio: MarsRealEstateGrid
- כדי לפשט את תהליך ניהול התמונות, אפשר להשתמש בספריית Glide כדי להוריד, לאחסן בזיכרון זמני, לפענח ולשמור במטמון תמונות באפליקציה.
- כדי לטעון תמונה מהאינטרנט, Glide צריך שני דברים: כתובת ה-URL של התמונה ואובייקט
ImageViewשבו התמונה תוצג. כדי לציין את האפשרויות האלה, משתמשים בשיטותload()ו-into()עם Glide. - Binding adapters הן שיטות הרחבה שמוצבות בין תצוגה לבין הנתונים שמשויכים לתצוגה הזו. מתאמי Binding מספקים התנהגות מותאמת אישית כשהנתונים משתנים, למשל, כדי להפעיל את Glide לטעינת תמונה מכתובת URL לתוך
ImageView. - מתאמי Binding הם שיטות הרחבה שמסומנות בהערה
@BindingAdapter. - כדי להוסיף אפשרויות לבקשת Glide, משתמשים בשיטה
apply(). לדוגמה, משתמשים ב-apply()עםplaceholder()כדי לציין drawable לטעינה, וב-apply()עםerror()כדי לציין drawable לשגיאה. - כדי ליצור רשת של תמונות, משתמשים ב-
RecyclerViewעםGridLayoutManager. - כדי לעדכן את רשימת המאפיינים כשהיא משתנה, משתמשים במתאם קישור בין
RecyclerViewלפריסה.
קורס ב-Udacity:
מסמכי תיעוד למפתחי Android:
אחר:
בקטע הזה מפורטות אפשרויות למשימות ביתיות לתלמידים שעובדים על ה-Codelab הזה כחלק מקורס בהנחיית מדריך. המורה צריך:
- אם צריך, מקצים שיעורי בית.
- להסביר לתלמידים איך להגיש מטלות.
- בודקים את שיעורי הבית.
אנשי ההוראה יכולים להשתמש בהצעות האלה כמה שרוצים, ומומלץ להם להקצות כל שיעורי בית אחרים שהם חושבים שמתאימים.
אם אתם עובדים על ה-codelab הזה לבד, אתם יכולים להשתמש במשימות האלה כדי לבדוק את הידע שלכם.
עונים על השאלות הבאות
שאלה 1
באיזו שיטה של Glide השתמשת כדי לציין את ImageView שיכיל את התמונה שנטענה?
▢ into()
▢ with()
▢ imageview()
▢ apply()
שאלה 2
איך מציינים תמונת פלייסהולדר שתוצג בזמן הטעינה של Glide?
▢ שימוש בשיטה into() עם drawable.
▢ משתמשים ב-RequestOptions() ומפעילים את השיטה placeholder() עם drawable.
▢ מקצים את המאפיין Glide.placeholder ל-drawable.
▢ משתמשים ב-RequestOptions() ומפעילים את השיטה loadingImage() עם drawable.
שאלה 3
איך מציינים ששיטה היא מתאם קישור?
▢ מפעילים את ה-method setBindingAdapter() ב-LiveData.
▢ מעבירים את השיטה לקובץ Kotlin בשם BindingAdapters.kt.
▢ משתמשים במאפיין android:adapter בפריסת ה-XML.
▢ מוסיפים את ההערה @BindingAdapter לשיטה.
להתחלת השיעור הבא:
קישורים ל-codelabs אחרים בקורס הזה מופיעים בדף הנחיתה של ה-codelabs בנושא יסודות Android Kotlin.