ה-codelab הזה הוא חלק מהקורס Android Kotlin Fundamentals. כדי להפיק את המרב מהקורס הזה, מומלץ לעבוד על ה-codelabs לפי הסדר. כל ה-codelab של הקורס מפורטים בדף הנחיתה של ה-codelab בנושא יסודות Kotlin ל-Android.
מבוא
בשיעורי ה-Lab הקודמים של השיעור הזה למדתם איך לקבל נתונים על נדל"ן במאדים משירות אינטרנט, ואיך ליצור RecyclerView עם פריסת רשת כדי לטעון ולהציג תמונות מהנתונים האלה. ב-codelab הזה, תסיימו את האפליקציה MarsRealEstate על ידי הטמעת האפשרות לסנן את הנכסים במאדים לפי הזמינות שלהם להשכרה או לרכישה. אתם גם יוצרים תצוגה מפורטת, כך שאם המשתמש יקיש על תמונה של נכס בתצוגת הסקירה הכללית, הוא יראה תצוגה מפורטת עם פרטים על הנכס הזה.
מה שכדאי לדעת
- איך יוצרים קטעים ומשתמשים בהם.
- איך לנווט בין פרגמנטים ולהשתמש ב-Safe Args (תוסף Gradle) כדי להעביר נתונים בין פרגמנטים.
- איך משתמשים ברכיבי ארכיטקטורה, כולל מודלים של תצוגה, מפעלים של מודלים של תצוגה, טרנספורמציות ו-
LiveData. - איך לאחזר נתונים מקודדים ב-JSON משירות אינטרנט מסוג REST ולנתח את הנתונים לאובייקטים של Kotlin באמצעות הספריות Retrofit ו-Moshi.
מה תלמדו
- איך משתמשים בביטויי שיוך מורכבים בקובצי הפריסה.
- איך שולחים בקשות Retrofit לשירות אינטרנט עם אפשרויות שאילתה.
מה עושים
- לשנות את אפליקציית MarsRealEstate כדי לסמן את הנכסים במאדים שמוצעים למכירה (לעומת אלה שמוצעים להשכרה) באמצעות סמל של סימן דולר.
- משתמשים בתפריט האפשרויות בדף הסקירה הכללית כדי ליצור בקשה לשירות אינטרנט שמסננת את מאפייני Mars לפי סוג.
- יוצרים רכיב פרטים לנכס Mars, מקשרים את הרכיב הזה לרשת הסקירה הכללית באמצעות ניווט ומעבירים את נתוני הנכס לרכיב הזה.
ב-Codelab הזה (וב-Codelabs קשורים) עובדים עם אפליקציה בשם MarsRealEstate, שמציגה נכסים למכירה במאדים. האפליקציה הזו מתחברת לשרת אינטרנט כדי לאחזר ולהציג נתוני נכסים, כולל פרטים כמו המחיר והאם הנכס זמין למכירה או להשכרה. התמונות שמייצגות כל נכס הן תמונות אמיתיות ממאדים שצולמו על ידי הרוברים של נאס"א על מאדים. בסדנאות קודמות, יצרתם RecyclerView עם פריסת רשת לכל התמונות של הנכס:

בגרסה הזו של האפליקציה, אתם עובדים עם סוג הנכס (השכרה לעומת קנייה) ומוסיפים סמל לפריסת הרשת כדי לסמן נכסים שמוצעים למכירה:

משנים את תפריט האפשרויות של האפליקציה כדי לסנן את הרשת כך שיוצגו רק הנכסים שמוצעים להשכרה או למכירה:

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

עד עכשיו, החלק היחיד בנתוני הנכס של Mars שבו השתמשתם הוא כתובת ה-URL של תמונת הנכס. אבל נתוני הנכס – שהגדרתם במחלקה MarsProperty – כוללים גם מזהה, מחיר וסוג (להשכרה או למכירה). כדי לרענן את הזיכרון, הנה קטע מנתוני ה-JSON שמתקבלים משירות האינטרנט:
{
"price":8000000,
"id":"424908",
"type":"rent",
"img_src": "http://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631290305226E03_DXXX.jpg"
},במשימה הזו, מתחילים לעבוד עם סוג הנכס Mars כדי להוסיף תמונה של סימן דולר לנכסים שמוצגים בדף הסקירה הכללית ומוצעים למכירה.
שלב 1: מעדכנים את MarsProperty כך שיכלול את הסוג
הסיווג MarsProperty מגדיר את מבנה הנתונים של כל נכס שמסופק על ידי שירות האינטרנט. בסדנת קוד קודמת, השתמשתם בספריית Moshi כדי לנתח את תגובת ה-JSON הגולמית משירות האינטרנט של מאדים לאובייקטים נפרדים של נתוני MarsProperty.
בשלב הזה, מוסיפים קצת לוגיקה למחלקה MarsProperty כדי לציין אם נכס מוצע להשכרה או לא (כלומר, אם הסוג הוא המחרוזת "rent" או "buy"). תשתמשו בלוגיקה הזו ביותר ממקום אחד, ולכן עדיף להוסיף אותה כאן במחלקת הנתונים ולא לשכפל אותה.
- פותחים את אפליקציית MarsRealEstate מה-codelab האחרון. (אם האפליקציה לא מותקנת במכשיר, אפשר להוריד את MarsRealEstateGrid).
- פתיחת
network/MarsProperty.kt. מוסיפים גוף להגדרת המחלקהMarsPropertyומוסיפים שיטת getter מותאמת אישית ל-isRentalשמחזירהtrueאם האובייקט הוא מסוג"rent".
data class MarsProperty(
val id: String,
@Json(name = "img_src") val imgSrcUrl: String,
val type: String,
val price: Double) {
val isRental
get() = type == "rent"
}שלב 2: מעדכנים את פריסת הפריטים ברשת
עכשיו מעדכנים את פריסת הפריט עבור רשת התמונות כדי להציג את הציור של סימן הדולר רק בתמונות של הנכסים שמוצעים למכירה:

בעזרת ביטויי קשירת נתונים, אפשר לבצע את הבדיקה הזו באופן מלא בפריסת ה-XML של הפריטים ברשת.
- פתיחת
res/layout/grid_view_item.xml. זהו קובץ הפריסה של כל תא בפריסת הרשת שלRecyclerView. בשלב הזה, הקובץ מכיל רק את רכיב<ImageView>של תמונת הנכס. - בתוך האלמנט
<data>, מוסיפים אלמנט<import>בשביל המחלקהView. משתמשים בייבוא כשרוצים להשתמש ברכיבים של מחלקה בתוך ביטוי של קשירת נתונים בקובץ פריסה. במקרה הזה, תשתמשו בקבועיםView.GONEו-View.VISIBLE, ולכן תצטרכו גישה למחלקהView.
<import type="android.view.View"/>- מקיפים את כל תצוגת התמונה בתג
FrameLayout, כדי לאפשר להציג את ציור סימן הדולר מעל תמונת הנכס.
<FrameLayout
android:layout_width="match_parent"
android:layout_height="170dp">
<ImageView
android:id="@+id/mars_image"
...
</FrameLayout>- במאפיין
ImageView, משנים את המאפייןandroid:layout_heightלערךmatch_parentכדי למלא את מאפיין ההורה החדשFrameLayout.
android:layout_height="match_parent"- מוסיפים רכיב
<ImageView>שני ממש מתחת לראשון, בתוךFrameLayout. משתמשים בהגדרה שמוצגת למטה. התמונה הזו מופיעה בפינה הימנית התחתונה של פריט הרשת, מעל תמונת מאדים, והיא משתמשת ב-drawable שמוגדר ב-res/drawable/ic_for_sale_outline.xmlעבור סמל סימן הדולר.
<ImageView
android:id="@+id/mars_property_type"
android:layout_width="wrap_content"
android:layout_height="45dp"
android:layout_gravity="bottom|end"
android:adjustViewBounds="true"
android:padding="5dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_for_sale_outline"
tools:src="@drawable/ic_for_sale_outline"/>- מוסיפים את מאפיין
android:visibilityלתצוגת התמונהmars_property_type. משתמשים בביטוי קשירה כדי לבדוק את סוג המאפיין, ומקצים את הנראות ל-View.GONE(להשכרה) או ל-View.VISIBLE(לרכישה).
android:visibility="@{property.rental ? View.GONE : View.VISIBLE}"עד עכשיו ראיתם ביטויי קשירה רק בפריסות שמשתמשות במשתנים נפרדים שהוגדרו ברכיב <data>. ביטויים של קישור הם כלי רב עוצמה שמאפשר לבצע פעולות כמו בדיקות וחישובים מתמטיים, והכול בתוך פריסת ה-XML. במקרה כזה, משתמשים באופרטור הטרינרי (?:) כדי לבצע בדיקה (האם האובייקט הזה הוא להשכרה?). אתם מספקים תוצאה אחת לערך true (הסתרת סמל סימן הדולר עם View.GONE) ותוצאה אחרת לערך false (הצגת הסמל עם View.VISIBLE).
קובץ grid_view_item.xml המלא החדש מוצג בהמשך:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.view.View"/>
<variable
name="property"
type="com.example.android.marsrealestate.network.MarsProperty" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="170dp">
<ImageView
android:id="@+id/mars_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:adjustViewBounds="true"
android:padding="2dp"
app:imageUrl="@{property.imgSrcUrl}"
tools:src="@tools:sample/backgrounds/scenic"/>
<ImageView
android:id="@+id/mars_property_type"
android:layout_width="wrap_content"
android:layout_height="45dp"
android:layout_gravity="bottom|end"
android:adjustViewBounds="true"
android:padding="5dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_for_sale_outline"
android:visibility="@{property.rental ? View.GONE : View.VISIBLE}"
tools:src="@drawable/ic_for_sale_outline"/>
</FrameLayout>
</layout>- קומפלו את האפליקציה והריצו אותה. שימו לב שהמאפיינים שלא מושכרים מסומנים בסמל של סימן הדולר.

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

אחת הדרכים לבצע את המשימה הזו היא לבדוק את הסוג של כל MarsProperty בטבלת הסקירה הכללית ולהציג רק את המאפיינים התואמים. עם זאת, בשירות האינטרנט בפועל של מאדים יש פרמטר או אפשרות של שאילתה (שנקראים filter) שמאפשרים לקבל רק מאפיינים מסוג rent או מסוג buy. אפשר להשתמש בשאילתת הסינון הזו עם כתובת ה-URL של שירות האינטרנט realestate בדפדפן באופן הבא:
https://android-kotlin-fun-mars-server.appspot.com/realestate?filter=buyבמשימה הזו משנים את המחלקה MarsApiService כדי להוסיף אפשרות שאילתה לבקשה של שירות האינטרנט באמצעות Retrofit. לאחר מכן, מחברים את תפריט האפשרויות כדי להוריד מחדש את כל נתוני הנכס Mars באמצעות אפשרות השאילתה הזו. מכיוון שהתגובה שמתקבלת משירות האינטרנט מכילה רק את המאפיינים שמעניינים אתכם, אתם לא צריכים לשנות את לוגיקת התצוגה של התצוגה המקדימה של הרשת.
שלב 1: מעדכנים את שירות Mars API
כדי לשנות את הבקשה, צריך לחזור לכיתה MarsApiService שהטמעתם ב-codelab הראשון בסדרה הזו. משנים את הסיווג כדי לספק API לסינון.
- פתיחת
network/MarsApiService.kt. מתחת לשורות הייבוא, יוצריםenumבשםMarsApiFilterכדי להגדיר קבועים שתואמים לערכי השאילתות ששירות האינטרנט מצפה לקבל.
enum class MarsApiFilter(val value: String) {
SHOW_RENT("rent"),
SHOW_BUY("buy"),
SHOW_ALL("all") }- משנים את השיטה
getProperties()כך שתקבל קלט של מחרוזת לשאילתת הסינון, ומבצעים הערה לקלט באמצעות@Query("filter"), כמו שמוצג בהמשך.
כשמופיעה בקשה, לוחצים עלretrofit2.http.Queryייבוא.
ההערה@Queryאומרת לשיטהgetProperties()(ולכן ל-Retrofit) לבצע את הבקשה לשירות האינטרנט עם אפשרות הסינון. בכל פעם שמתבצעת קריאה ל-getProperties(), כתובת ה-URL של הבקשה כוללת את החלק?filter=type, שמנחה את שירות האינטרנט להגיב עם תוצאות שתואמות לשאילתה הזו.
fun getProperties(@Query("filter") type: String): שלב 2: עדכון מודל התצוגה של סקירת החשבון
אתם מבקשים נתונים מ-MarsApiService באמצעות השיטה getMarsRealEstateProperties() ב-OverviewViewModel. עכשיו צריך לעדכן את הבקשה כדי להוסיף את ארגומנט המסנן.
- פתיחת
overview/OverviewViewModel.kt. יוצגו שגיאות ב-Android Studio בגלל השינויים שביצעתם בשלב הקודם. מוסיפים אתMarsApiFilter(ה-enum של ערכי המסנן האפשריים) כפרמטר לקריאהgetMarsRealEstateProperties().
מייבאים אתcom.example.android.marsrealestate.network.MarsApiFilterכשמתבקשים.
private fun getMarsRealEstateProperties(filter: MarsApiFilter) {- משנים את הקריאה ל-
getProperties()בשירות Retrofit כדי להעביר את שאילתת המסנן הזו כמחרוזת.
var getPropertiesDeferred = MarsApi.retrofitService.getProperties(filter.value)- בבלוק
init {}, מעבירים אתMarsApiFilter.SHOW_ALLכארגומנט ל-getMarsRealEstateProperties(), כדי להציג את כל המאפיינים כשהאפליקציה נטענת בפעם הראשונה.
init {
getMarsRealEstateProperties(MarsApiFilter.SHOW_ALL)
}- בסוף הכיתה, מוסיפים method
updateFilter()שמקבל ארגומנטMarsApiFilterוקורא ל-getMarsRealEstateProperties()עם הארגומנט הזה.
fun updateFilter(filter: MarsApiFilter) {
getMarsRealEstateProperties(filter)
}שלב 3: קישור ה-fragment לתפריט האפשרויות
השלב האחרון הוא לחבר את תפריט האפשרויות הנוספות אל ה-Fragment כדי להפעיל את updateFilter() ב-ViewModel כשהמשתמש בוחר באפשרות בתפריט.
- פתיחת
res/menu/overflow_menu.xml. באפליקציית MarsRealEstate יש תפריט overflow קיים שמציג את שלוש האפשרויות הזמינות: הצגת כל הנכסים, הצגת נכסים להשכרה בלבד והצגת נכסים למכירה בלבד.
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/show_all_menu"
android:title="@string/show_all" />
<item
android:id="@+id/show_rent_menu"
android:title="@string/show_rent" />
<item
android:id="@+id/show_buy_menu"
android:title="@string/show_buy" />
</menu>- פתיחת
overview/OverviewFragment.kt. בסוף השיעור, מטמיעים את השיטהonOptionsItemSelected()כדי לטפל בבחירות של פריטים בתפריט.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
} - ב-
onOptionsItemSelected(), קוראים לשיטתupdateFilter()במודל התצוגה עם המסנן המתאים. משתמשים בבלוקwhen {}של Kotlin כדי לעבור בין האפשרויות. משתמשים בערךMarsApiFilter.SHOW_ALLכערך ברירת המחדל של המסנן. החזרתtrue, כי טיפלת בפריט בתפריט. מייבאים אתMarsApiFilter(com.example.android.marsrealestate.network.MarsApiFilter) כשמתבקשים. השיטה המלאהonOptionsItemSelected()מוצגת בהמשך.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
viewModel.updateFilter(
when (item.itemId) {
R.id.show_rent_menu -> MarsApiFilter.SHOW_RENT
R.id.show_buy_menu -> MarsApiFilter.SHOW_BUY
else -> MarsApiFilter.SHOW_ALL
}
)
return true
}- קומפילציה והרצה של האפליקציה. האפליקציה מפעילה את רשת הסקירה הכללית הראשונה עם כל סוגי הנכסים, והנכסים שמוצעים למכירה מסומנים בסמל הדולר.
- בתפריט האפשרויות, בוחרים באפשרות השכרה. הנכסים נטענים מחדש ואף אחד מהם לא מופיע עם סמל הדולר. (מוצגים רק נכסים להשכרה). יכול להיות שתצטרכו להמתין כמה רגעים עד שהתצוגה תתרענן ויוצגו רק הנכסים המסוננים.
- בתפריט האפשרויות, בוחרים באפשרות קנייה. הנכסים נטענים מחדש, וכולם מופיעים עם סמל הדולר. (מוצגים רק נכסים למכירה).
עכשיו מוצגת רשת של סמלים של נכסי Mars, אבל הגיע הזמן לקבל פרטים נוספים. במשימה הזו, מוסיפים קטע פרטים כדי להציג את הפרטים של מאפיין ספציפי. בקטע הפרטים יוצגו תמונה גדולה יותר, המחיר וסוג הנכס – אם הוא להשכרה או למכירה.

הקטע הזה מופעל כשמשתמש מקיש על תמונה ברשת התמונות בתצוגה הכללית. כדי לעשות זאת, צריך להוסיף מאזין onClick לפריטי הרשת RecyclerView ואז לעבור אל הפריט החדש. כדי לנווט, מפעילים שינוי ב-LiveData ViewModel, כמו שעשיתם לאורך השיעורים האלה. בנוסף, משתמשים בתוסף Safe Args של רכיב הניווט כדי להעביר את פרטי MarsProperty שנבחרו מהקטע של הסקירה הכללית לקטע של הפרטים.
שלב 1: יוצרים את מודל תצוגת הפרטים ומעדכנים את פריסת הפרטים
בדומה לתהליך שבו השתמשתם עבור מודל התצוגה המפורטת והקטעים, עכשיו אתם צריכים להטמיע את מודל התצוגה המפורטת ואת קובצי הפריסה של קטע הפרטים.
- פתיחת
detail/DetailViewModel.kt. בדומה לקובצי Kotlin שקשורים לרשת שנמצאים בתיקייהnetworkולקובצי הסקירה הכללית שנמצאים בתיקייהoverview, התיקייהdetailמכילה את הקבצים שמשויכים לתצוגת הפרטים. שימו לב שהמחלקהDetailViewModel(שכרגע ריקה) מקבלת אתmarsPropertyכפרמטר בבונה.
class DetailViewModel( marsProperty: MarsProperty,
app: Application) : AndroidViewModel(app) {
}- בתוך הגדרת הכיתה, מוסיפים
LiveDataעבור מאפיין מארס שנבחר, כדי לחשוף את המידע הזה בתצוגה המפורטת. פועלים לפי הדפוס הרגיל של יצירתMutableLiveDataכדי להכיל אתMarsPropertyעצמו, ואז חושפים מאפייןLiveDataציבורי שלא ניתן לשינוי.
מייבאים אתandroidx.lifecycle.LiveDataומייבאים אתandroidx.lifecycle.MutableLiveDataכשמתבקשים.
private val _selectedProperty = MutableLiveData<MarsProperty>()
val selectedProperty: LiveData<MarsProperty>
get() = _selectedProperty- יוצרים בלוק
init {}ומגדירים את הערך של מאפיין Mars שנבחר באמצעות האובייקטMarsPropertyמהקונסטרוקטור.
init {
_selectedProperty.value = marsProperty
}- פותחים את
res/layout/fragment_detail.xmlומסתכלים עליו בתצוגת העיצוב.
זהו קובץ הפריסה של קטע הפרטים. הוא מכילImageViewלתמונה הגדולה,TextViewלסוג הנכס (השכרה או מכירה) וTextViewלמחיר. שימו לב שפריסת האילוצים עטופה בתגScrollView, כך שהיא תגולל באופן אוטומטי אם התצוגה תהיה גדולה מדי, למשל כשהמשתמש יצפה בה במצב לרוחב. - עוברים לכרטיסייה טקסט כדי לראות את הפריסה. בחלק העליון של הפריסה, ממש לפני הרכיב
<ScrollView>, מוסיפים רכיב<data>כדי לשייך את מודל תצוגת הפרטים לפריסה.
<data>
<variable
name="viewModel"
type="com.example.android.marsrealestate.detail.DetailViewModel" />
</data>- מוסיפים את המאפיין
app:imageUrlלרכיבImageView. מגדירים אותו לערךimgSrcUrlמהמאפיין שנבחר במודל התצוגה המפורטת.
גם כאן ייעשה שימוש אוטומטי במתאם הקישור שמעמיס תמונה באמצעות Glide, כי המתאם הזה עוקב אחרי כל המאפיינים מסוגapp:imageUrl.
app:imageUrl="@{viewModel.selectedProperty.imgSrcUrl}"שלב 2: הגדרת הניווט במודל של תצוגת הסקירה הכללית
כשמשתמש מקיש על תמונה בתצוגת הסקירה הכללית, צריך להפעיל ניווט לקטע שמציג פרטים על הפריט שעליו הקיש המשתמש.
- פתיחת
overview/OverviewViewModel.kt. מוסיפים מאפיין_navigateToSelectedPropertyMutableLiveDataוחושפים אותו באמצעותLiveDataשלא ניתן לשינוי.
כשהערך שלLiveDataמשתנה לערך שאינו null, מופעל הניווט. (בקרוב תוסיפו את הקוד כדי לעקוב אחרי המשתנה הזה ולהפעיל את הניווט).
private val _navigateToSelectedProperty = MutableLiveData<MarsProperty>()
val navigateToSelectedProperty: LiveData<MarsProperty>
get() = _navigateToSelectedProperty- בסוף המחלקה, מוסיפים שיטת
displayPropertyDetails()שקובעת את הערך של _navigateToSelectedPropertyלנכס Mars שנבחר.
fun displayPropertyDetails(marsProperty: MarsProperty) {
_navigateToSelectedProperty.value = marsProperty
}- מוסיפים שיטה
displayPropertyDetailsComplete()שמאפסת את הערך של_navigateToSelectedProperty. הוא נחוץ כדי לסמן את מצב הניווט כהשלמה, וכדי למנוע את הפעלת הניווט שוב כשהמשתמש חוזר מתצוגת הפרטים.
fun displayPropertyDetailsComplete() {
_navigateToSelectedProperty.value = null
}שלב 3: הגדרת מאזיני הקליקים במתאם ובקטע של רשת
- פתיחת
overview/PhotoGridAdapter.kt. בסוף הכיתה, יוצרים מחלקה מותאמת אישיתOnClickListenerשמקבלת ביטוי למדא עם פרמטרmarsProperty. בתוך המחלקה, מגדירים פונקציהonClick()שמוגדרת לפרמטר הלמדה.
class OnClickListener(val clickListener: (marsProperty:MarsProperty) -> Unit) {
fun onClick(marsProperty:MarsProperty) = clickListener(marsProperty)
}- גוללים למעלה להגדרת המחלקה של
PhotoGridAdapterומוסיפים מאפיין פרטיOnClickListenerלקונסטרוקטור.
class PhotoGridAdapter( private val onClickListener: OnClickListener ) :
ListAdapter<MarsProperty,
PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {- כדי להוסיף לתמונה קישור שאפשר ללחוץ עליו, מוסיפים את התג
onClickListenerלפריט ברשת בשיטהonBindviewHolder(). מגדירים את מאזין הקליקים בין הקריאות ל-getItem() and bind().
override fun onBindViewHolder(holder: MarsPropertyViewHolder, position: Int) {
val marsProperty = getItem(position)
holder.itemView.setOnClickListener {
onClickListener.onClick(marsProperty)
}
holder.bind(marsProperty)
}- פתיחת
overview/OverviewFragment.kt. בשיטהonCreateView(), מחליפים את השורה שמאתחלת את המאפייןbinding.photosGrid.adapterבשורה שמוצגת למטה.
הקוד הזה מוסיף את האובייקטPhotoGridAdapter.onClickListenerלקונסטרוקטורPhotoGridAdapter, וקורא ל-viewModel.displayPropertyDetails()עם האובייקטMarsPropertyשמועבר. הפעולה הזו מפעילה אתLiveDataבמודל התצוגה של הניווט.
binding.photosGrid.adapter = PhotoGridAdapter(PhotoGridAdapter.OnClickListener {
viewModel.displayPropertyDetails(it)
})שלב 4: שינוי גרף הניווט והפיכת MarsProperty לניתן להעברה
כשמשתמש מקיש על תמונה ברשת התמונות של התצוגה הכללית, האפליקציה צריכה לנווט אל קטע הפרטים ולהעביר את הפרטים של הנכס שנבחר במאדים, כדי שבתצוגת הפרטים יוצג המידע הזה.

כרגע יש לכם click listener מ-PhotoGridAdapter לטיפול בהקשה, ודרך להפעיל את הניווט ממודל התצוגה. אבל עדיין לא מועבר אובייקט MarsProperty אל קטע הפרטים. לשם כך משתמשים ב-Safe Args מרכיב הניווט.
- פתיחת
res/navigation/nav_graph.xml. לוחצים על הכרטיסייה Text כדי לראות את קוד ה-XML של תרשים הניווט. - בתוך האלמנט
<fragment>של קטע הפרטים, מוסיפים את האלמנט<argument>שמוצג למטה. הארגומנט הזה, שנקראselectedProperty, הוא מסוגMarsProperty.
<argument
android:name="selectedProperty"
app:argType="com.example.android.marsrealestate.network.MarsProperty"
/>- קומפילציה של האפליקציה. הניווט נותן שגיאה כי
MarsPropertyלא ניתן להעברה. ממשקParcelableמאפשר לבצע סריאליזציה של אובייקטים, כך שניתן להעביר את נתוני האובייקטים בין פרגמנטים או פעילויות. במקרה כזה, כדי שהנתונים בתוך האובייקטMarsPropertyיועברו אל קטע הפרטים באמצעות Safe Args, האובייקטMarsPropertyצריך להטמיע את הממשקParcelable. החדשות הטובות הן ש-Kotlin מספקת קיצור דרך קל להטמעה של הממשק הזה. - פתיחת
network/MarsProperty.kt. מוסיפים את ההערה@Parcelizeלהגדרת המחלקה.
מייבאים אתkotlinx.android.parcel.Parcelizeכשמתבקשים.
ההערה@Parcelizeמשתמשת בתוספי Kotlin Android כדי להטמיע באופן אוטומטי את ה-methods בממשקParcelableעבור המחלקה הזו. לא צריך לעשות שום דבר אחר.
@Parcelize
data class MarsProperty (- משנים את הגדרת המחלקה של
MarsPropertyכדי להרחיב אתParcelable.
מייבאים אתandroid.os.Parcelableכשמתבקשים.
הגדרת המחלקהMarsPropertyנראית עכשיו כך:
@Parcelize
data class MarsProperty (
val id: String,
@Json(name = "img_src") val imgSrcUrl: String,
val type: String,
val price: Double) : Parcelable {שלב 5: חיבור המקטעים
אתם עדיין לא מנווטים – הניווט בפועל מתבצע בפרגמנטים. בשלב הזה מוסיפים את החלקים האחרונים להטמעת הניווט בין קטעי התצוגה של הסקירה הכללית והפרטים.
- פתיחת
overview/OverviewFragment.kt. ב-onCreateView(), מתחת לשורות שמאתחלות את המתאם של רשת התמונות, מוסיפים את השורות שמוצגות למטה כדי לצפות ב-navigatedToSelectedPropertyממודל התצוגה של התצוגה הכללית.
מייבאים אתandroidx.lifecycle.Observerומייבאים אתandroidx.navigation.fragment.findNavControllerכשמתבקשים.
האובייקט observer בודק אםMarsProperty–itב-lambda – הוא לא null, ואם כן, הוא מקבל את בקר הניווט מהקטע עםfindNavController(). מתקשרים אלdisplayPropertyDetailsComplete()כדי להורות למודל התצוגה לאפס אתLiveDataלמצב null, כדי שלא תופעל שוב בטעות ניווט כשהאפליקציה תחזור אלOverviewFragment.
viewModel.navigateToSelectedProperty.observe(this, Observer {
if ( null != it ) {
this.findNavController().navigate(
OverviewFragmentDirections.actionShowDetail(it))
viewModel.displayPropertyDetailsComplete()
}
})- פתיחת
detail/DetailFragment.kt. מוסיפים את השורה הזו ממש מתחת לקריאה ל-setLifecycleOwner()בשיטהonCreateView(). בשורה הזו מתקבל אובייקטMarsPropertyשנבחר מ-Safe Args.
שימו לב לשימוש באופרטור של Kotlin לאישור שערך הוא לא null (!!). אם האובייקטselectedPropertyלא קיים, משהו נורא קרה ואתם רוצים שהקוד יחזיר מצביע null. (בקוד ייצור, צריך לטפל בשגיאה הזו בצורה כלשהי).
val marsProperty = DetailFragmentArgs.fromBundle(arguments!!).selectedProperty- מוסיפים את השורה הזו כדי לקבל
DetailViewModelFactoryחדש. תשתמשו ב-DetailViewModelFactoryכדי לקבל מופע שלDetailViewModel. אפליקציית המתחילים כוללת הטמעה שלDetailViewModelFactory, כך שכל מה שצריך לעשות כאן הוא לאתחל אותה.
val viewModelFactory = DetailViewModelFactory(marsProperty, application)- לבסוף, מוסיפים את השורה הזו כדי לקבל
DetailViewModelמהמפעל ולחבר את כל החלקים.
binding.viewModel = ViewModelProviders.of(
this, viewModelFactory).get(DetailViewModel::class.java)- קומפילציה והרצה של האפליקציה, והקשה על תמונה של נכס של Mars. יוצג קטע הפרטים של המאפיין. מקישים על לחצן 'הקודם' כדי לחזור לדף הסקירה הכללית, ורואים שמסך הפרטים עדיין די דליל. במשימה הבאה מסיימים להוסיף את נתוני הנכס לדף הפרטים.
בשלב הזה, בדף הפרטים מוצגת רק אותה תמונה של מאדים שאתם רגילים לראות בדף הסקירה הכללית. בנוסף, המחלקה MarsProperty כוללת גם סוג נכס (להשכרה או לרכישה) ומחיר נכס. מסך הפרטים צריך לכלול את שני הערכים האלה, ומומלץ לציין בנכסי ההשכרה שהמחיר הוא ערך חודשי. כדי להטמיע את שתי הפעולות האלה, משתמשים בטרנספורמציות של LiveData במודל התצוגה.
- פתיחת
res/values/strings.xml. קוד המתחיל כולל משאבי מחרוזות, שמוצגים בהמשך, כדי לעזור לכם ליצור את המחרוזות לתצוגה המפורטת. למחיר, תשתמשו במשאבdisplay_price_monthly_rentalאו במשאבdisplay_price, בהתאם לסוג הנכס.
<string name="type_rent">Rent</string>
<string name="type_sale">Sale</string>
<string name="display_type">For %s</string>
<string name="display_price_monthly_rental">$%,.0f/month</string>
<string name="display_price">$%,.0f</string>- פתיחת
detail/DetailViewModel.kt. בתחתית הכיתה, מוסיפים את הקוד שמופיע למטה.
מייבאים אתandroidx.lifecycle.Transformationsאם מתבקשים.
הטרנספורמציה הזו בודקת אם הנכס שנבחר הוא נכס להשכרה, באמצעות אותה בדיקה מהמשימה הראשונה. אם הנכס הוא נכס להשכרה, הטרנספורמציה בוחרת את המחרוזת המתאימה מהמשאבים באמצעות מתג Kotlinwhen {}. לשני המחרוזות האלה צריך להוסיף מספר בסוף, ולכן צריך לשרשר אתproperty.priceאחרי כן.
val displayPropertyPrice = Transformations.map(selectedProperty) {
app.applicationContext.getString(
when (it.isRental) {
true -> R.string.display_price_monthly_rental
false -> R.string.display_price
}, it.price)
}- מייבאים את המחלקה
Rשנוצרה כדי לקבל גישה למשאבי המחרוזות בפרויקט.
import com.example.android.marsrealestate.R- אחרי
displayPropertyPriceהשינוי, מוסיפים את הקוד שמוצג למטה. הטרנספורמציה הזו משרשרת כמה משאבי מחרוזות, בהתאם לסוג הנכס (אם הוא להשכרה).
val displayPropertyType = Transformations.map(selectedProperty) {
app.applicationContext.getString(R.string.display_type,
app.applicationContext.getString(
when (it.isRental) {
true -> R.string.type_rent
false -> R.string.type_sale
}))
}- פתיחת
res/layout/fragment_detail.xml. נותר רק דבר אחד לעשות, והוא לקשר את המחרוזות החדשות (שיצרתם באמצעותLiveDataהטרנספורמציות) לתצוגת הפרטים. כדי לעשות את זה, מגדירים את הערך של שדה הטקסט של סוג הנכס ל-viewModel.displayPropertyType, ואת הערך של שדה הטקסט של ערך המחיר ל-viewModel.displayPropertyPrice.
<TextView
android:id="@+id/property_type_text"
...
android:text="@{viewModel.displayPropertyType}"
...
tools:text="To Rent" />
<TextView
android:id="@+id/price_value_text"
...
android:text="@{viewModel.displayPropertyPrice}"
...
tools:text="$100,000" />- קומפלו את האפליקציה והריצו אותה. עכשיו כל נתוני הנכס מופיעים בדף הפרטים, בפורמט מסודר.

פרויקט Android Studio: MarsRealEstateFinal
ביטויים של קישור
- אפשר להשתמש בביטויי קישור בקובצי פריסה של XML כדי לבצע פעולות תכנות פשוטות, כמו חישובים מתמטיים או בדיקות מותנות, על נתונים מקושרים.
- כדי להפנות למחלקות בתוך קובץ הפריסה, משתמשים בתג
<import>בתוך התג<data>.
אפשרויות לשאילתות של שירותי אינטרנט
- בקשות לשירותי אינטרנט יכולות לכלול פרמטרים אופציונליים.
- כדי לציין פרמטרים של שאילתה בבקשה, משתמשים באנוטציה
@Queryב-Retrofit.
קורס ב-Udacity:
מסמכי תיעוד למפתחי Android:
- סקירה כללית של ViewModel
- סקירה כללית של LiveData
- מתאמי Binding
- פריסות וביטויי קשירה
- ניווט
- איך מתחילים להשתמש ברכיב הניווט
- העברת נתונים בין יעדים (כולל תיאור של Safe Args)
- כיתה
Transformations - כיתה
ViewModelProvider - כיתה
ViewModelProvider.Factory
אחר:
בקטע הזה מפורטות אפשרויות למשימות ביתיות לתלמידים שעובדים על ה-Codelab הזה כחלק מקורס בהנחיית מדריך. המורה צריך:
- אם צריך, מקצים שיעורי בית.
- להסביר לתלמידים איך להגיש מטלות.
- בודקים את שיעורי הבית.
אנשי ההוראה יכולים להשתמש בהצעות האלה כמה שרוצים, ומומלץ להם להקצות כל שיעורי בית אחרים שהם חושבים שמתאימים.
אם אתם עובדים על ה-codelab הזה לבד, אתם יכולים להשתמש במשימות האלה כדי לבדוק את הידע שלכם.
עונים על השאלות הבאות
שאלה 1
מה עושה התג <import> בקובץ פריסה בפורמט XML?
▢ Include one layout file in another.
▢ הטמעת קוד Kotlin בקובץ הפריסה.
▢ מתן גישה לנכסים שמקושרים לנתונים.
▢ מאפשרות לכם להפנות לכיתות ולתלמידים בכיתות בביטויי קשירה.
שאלה 2
איך מוסיפים אפשרות שאילתה לקריאה לשירות אינטרנט REST ב-Retrofit?
▢ צירוף השאילתה לסוף כתובת ה-URL של הבקשה.
▢ מוסיפים פרמטר לשאילתה לפונקציה שיוצרת את הבקשה, ומציינים את הפרמטר באמצעות @Query.
▢ משתמשים במחלקה Query כדי ליצור בקשה.
▢ משתמשים בשיטת addQuery() ב-Retrofit builder.
לשיעור הבא:
קישורים ל-codelabs אחרים בקורס הזה מופיעים בדף הנחיתה של ה-codelabs בנושא יסודות Android Kotlin.