‫Android Kotlin Fundamentals 08.3 Filtering and detail views with internet data

ה-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"). תשתמשו בלוגיקה הזו ביותר ממקום אחד, ולכן עדיף להוסיף אותה כאן במחלקת הנתונים ולא לשכפל אותה.

  1. פותחים את אפליקציית MarsRealEstate מה-codelab האחרון. (אם האפליקציה לא מותקנת במכשיר, אפשר להוריד את MarsRealEstateGrid).
  2. פתיחת 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 של הפריטים ברשת.

  1. פתיחת res/layout/grid_view_item.xml. זהו קובץ הפריסה של כל תא בפריסת הרשת של RecyclerView. בשלב הזה, הקובץ מכיל רק את רכיב <ImageView> של תמונת הנכס.
  2. בתוך האלמנט <data>, מוסיפים אלמנט <import> בשביל המחלקה View. משתמשים בייבוא כשרוצים להשתמש ברכיבים של מחלקה בתוך ביטוי של קשירת נתונים בקובץ פריסה. במקרה הזה, תשתמשו בקבועים View.GONE ו-View.VISIBLE, ולכן תצטרכו גישה למחלקה View.
<import type="android.view.View"/>
  1. מקיפים את כל תצוגת התמונה בתג FrameLayout, כדי לאפשר להציג את ציור סימן הדולר מעל תמונת הנכס.
<FrameLayout
   android:layout_width="match_parent"
   android:layout_height="170dp">
             <ImageView 
                    android:id="@+id/mars_image"
            ...
</FrameLayout>
  1. במאפיין ImageView, משנים את המאפיין android:layout_height לערך match_parent כדי למלא את מאפיין ההורה החדש FrameLayout.
android:layout_height="match_parent"
  1. מוסיפים רכיב <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"/>
  1. מוסיפים את מאפיין 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>
  1. קומפלו את האפליקציה והריצו אותה. שימו לב שהמאפיינים שלא מושכרים מסומנים בסמל של סימן הדולר.

כרגע מוצגים באפליקציה שלך כל הנכסים של 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 לסינון.

  1. פתיחת network/MarsApiService.kt. מתחת לשורות הייבוא, יוצרים enum בשם MarsApiFilter כדי להגדיר קבועים שתואמים לערכי השאילתות ששירות האינטרנט מצפה לקבל.
enum class MarsApiFilter(val value: String) {
   SHOW_RENT("rent"),
   SHOW_BUY("buy"),
   SHOW_ALL("all") }
  1. משנים את השיטה getProperties() כך שתקבל קלט של מחרוזת לשאילתת הסינון, ומבצעים הערה לקלט באמצעות @Query("filter"), כמו שמוצג בהמשך.

    כשמופיעה בקשה, לוחצים על retrofit2.http.Queryייבוא. ‫

    ההערה @Query אומרת לשיטה getProperties() (ולכן ל-Retrofit) לבצע את הבקשה לשירות האינטרנט עם אפשרות הסינון. בכל פעם שמתבצעת קריאה ל-getProperties(), כתובת ה-URL של הבקשה כוללת את החלק ?filter=type, שמנחה את שירות האינטרנט להגיב עם תוצאות שתואמות לשאילתה הזו.
fun getProperties(@Query("filter") type: String):  

שלב 2: עדכון מודל התצוגה של סקירת החשבון

אתם מבקשים נתונים מ-MarsApiService באמצעות השיטה getMarsRealEstateProperties() ב-OverviewViewModel. עכשיו צריך לעדכן את הבקשה כדי להוסיף את ארגומנט המסנן.

  1. פתיחת overview/OverviewViewModel.kt. יוצגו שגיאות ב-Android Studio בגלל השינויים שביצעתם בשלב הקודם. מוסיפים את MarsApiFilter (ה-enum של ערכי המסנן האפשריים) כפרמטר לקריאה getMarsRealEstateProperties().

    מייבאים את com.example.android.marsrealestate.network.MarsApiFilter כשמתבקשים.
private fun getMarsRealEstateProperties(filter: MarsApiFilter) {
  1. משנים את הקריאה ל-getProperties() בשירות Retrofit כדי להעביר את שאילתת המסנן הזו כמחרוזת.
var getPropertiesDeferred = MarsApi.retrofitService.getProperties(filter.value)
  1. בבלוק init {}, מעבירים את MarsApiFilter.SHOW_ALL כארגומנט ל-getMarsRealEstateProperties(), כדי להציג את כל המאפיינים כשהאפליקציה נטענת בפעם הראשונה.
init {
   getMarsRealEstateProperties(MarsApiFilter.SHOW_ALL)
}
  1. בסוף הכיתה, מוסיפים method‏ updateFilter() שמקבל ארגומנט MarsApiFilter וקורא ל-getMarsRealEstateProperties() עם הארגומנט הזה.
fun updateFilter(filter: MarsApiFilter) {
   getMarsRealEstateProperties(filter)
}

שלב 3: קישור ה-fragment לתפריט האפשרויות

השלב האחרון הוא לחבר את תפריט האפשרויות הנוספות אל ה-Fragment כדי להפעיל את updateFilter() ב-ViewModel כשהמשתמש בוחר באפשרות בתפריט.

  1. פתיחת 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>
  1. פתיחת overview/OverviewFragment.kt. בסוף השיעור, מטמיעים את השיטה onOptionsItemSelected() כדי לטפל בבחירות של פריטים בתפריט.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
} 
  1. ב-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
}
  1. קומפילציה והרצה של האפליקציה. האפליקציה מפעילה את רשת הסקירה הכללית הראשונה עם כל סוגי הנכסים, והנכסים שמוצעים למכירה מסומנים בסמל הדולר.
  2. בתפריט האפשרויות, בוחרים באפשרות השכרה. הנכסים נטענים מחדש ואף אחד מהם לא מופיע עם סמל הדולר. (מוצגים רק נכסים להשכרה). יכול להיות שתצטרכו להמתין כמה רגעים עד שהתצוגה תתרענן ויוצגו רק הנכסים המסוננים.
  3. בתפריט האפשרויות, בוחרים באפשרות קנייה. הנכסים נטענים מחדש, וכולם מופיעים עם סמל הדולר. (מוצגים רק נכסים למכירה).

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

הקטע הזה מופעל כשמשתמש מקיש על תמונה ברשת התמונות בתצוגה הכללית. כדי לעשות זאת, צריך להוסיף מאזין onClick לפריטי הרשת RecyclerView ואז לעבור אל הפריט החדש. כדי לנווט, מפעילים שינוי ב-LiveData ViewModel, כמו שעשיתם לאורך השיעורים האלה. בנוסף, משתמשים בתוסף Safe Args של רכיב הניווט כדי להעביר את פרטי MarsProperty שנבחרו מהקטע של הסקירה הכללית לקטע של הפרטים.

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

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

  1. פתיחת detail/DetailViewModel.kt. בדומה לקובצי Kotlin שקשורים לרשת שנמצאים בתיקייה network ולקובצי הסקירה הכללית שנמצאים בתיקייה overview, התיקייה detail מכילה את הקבצים שמשויכים לתצוגת הפרטים. שימו לב שהמחלקה DetailViewModel (שכרגע ריקה) מקבלת את marsProperty כפרמטר בבונה.
class DetailViewModel( marsProperty: MarsProperty,
                     app: Application) : AndroidViewModel(app) {
}
  1. בתוך הגדרת הכיתה, מוסיפים LiveData עבור מאפיין מארס שנבחר, כדי לחשוף את המידע הזה בתצוגה המפורטת. פועלים לפי הדפוס הרגיל של יצירת MutableLiveData כדי להכיל את MarsProperty עצמו, ואז חושפים מאפיין LiveData ציבורי שלא ניתן לשינוי.

    מייבאים את androidx.lifecycle.LiveData ומייבאים את androidx.lifecycle.MutableLiveData כשמתבקשים.
private val _selectedProperty = MutableLiveData<MarsProperty>()
val selectedProperty: LiveData<MarsProperty>
   get() = _selectedProperty
  1. יוצרים בלוק init {} ומגדירים את הערך של מאפיין Mars שנבחר באמצעות האובייקט MarsProperty מהקונסטרוקטור.
    init {
        _selectedProperty.value = marsProperty
    }
  1. פותחים את res/layout/fragment_detail.xml ומסתכלים עליו בתצוגת העיצוב.

    זהו קובץ הפריסה של קטע הפרטים. הוא מכיל ImageView לתמונה הגדולה, TextView לסוג הנכס (השכרה או מכירה) וTextView למחיר. שימו לב שפריסת האילוצים עטופה בתג ScrollView, כך שהיא תגולל באופן אוטומטי אם התצוגה תהיה גדולה מדי, למשל כשהמשתמש יצפה בה במצב לרוחב.
  2. עוברים לכרטיסייה טקסט כדי לראות את הפריסה. בחלק העליון של הפריסה, ממש לפני הרכיב <ScrollView>, מוסיפים רכיב <data> כדי לשייך את מודל תצוגת הפרטים לפריסה.
<data>
   <variable
       name="viewModel"
       type="com.example.android.marsrealestate.detail.DetailViewModel" />
</data>
  1. מוסיפים את המאפיין app:imageUrl לרכיב ImageView. מגדירים אותו לערך imgSrcUrl מהמאפיין שנבחר במודל התצוגה המפורטת.

    גם כאן ייעשה שימוש אוטומטי במתאם הקישור שמעמיס תמונה באמצעות Glide, כי המתאם הזה עוקב אחרי כל המאפיינים מסוג app:imageUrl.
 app:imageUrl="@{viewModel.selectedProperty.imgSrcUrl}"

שלב 2: הגדרת הניווט במודל של תצוגת הסקירה הכללית

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

  1. פתיחת overview/OverviewViewModel.kt. מוסיפים מאפיין _navigateToSelectedProperty MutableLiveData וחושפים אותו באמצעות LiveData שלא ניתן לשינוי.

    כשהערך של LiveData משתנה לערך שאינו null, מופעל הניווט. (בקרוב תוסיפו את הקוד כדי לעקוב אחרי המשתנה הזה ולהפעיל את הניווט).
private val _navigateToSelectedProperty = MutableLiveData<MarsProperty>()
val navigateToSelectedProperty: LiveData<MarsProperty>
   get() = _navigateToSelectedProperty
  1. בסוף המחלקה, מוסיפים שיטת displayPropertyDetails() שקובעת את הערך של _navigateToSelectedProperty לנכס Mars שנבחר.
fun displayPropertyDetails(marsProperty: MarsProperty) {
   _navigateToSelectedProperty.value = marsProperty
}
  1. מוסיפים שיטה displayPropertyDetailsComplete() שמאפסת את הערך של _navigateToSelectedProperty. הוא נחוץ כדי לסמן את מצב הניווט כהשלמה, וכדי למנוע את הפעלת הניווט שוב כשהמשתמש חוזר מתצוגת הפרטים.
fun displayPropertyDetailsComplete() {
   _navigateToSelectedProperty.value = null
}

שלב 3: הגדרת מאזיני הקליקים במתאם ובקטע של רשת

  1. פתיחת overview/PhotoGridAdapter.kt. בסוף הכיתה, יוצרים מחלקה מותאמת אישית OnClickListener שמקבלת ביטוי למדא עם פרמטר marsProperty. בתוך המחלקה, מגדירים פונקציה onClick() שמוגדרת לפרמטר הלמדה.
class OnClickListener(val clickListener: (marsProperty:MarsProperty) -> Unit) {
     fun onClick(marsProperty:MarsProperty) = clickListener(marsProperty)
}
  1. גוללים למעלה להגדרת המחלקה של PhotoGridAdapter ומוסיפים מאפיין פרטי OnClickListener לקונסטרוקטור.
class PhotoGridAdapter( private val onClickListener: OnClickListener ) :
       ListAdapter<MarsProperty,              
           PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
  1. כדי להוסיף לתמונה קישור שאפשר ללחוץ עליו, מוסיפים את התג 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)
}
  1. פתיחת 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 מרכיב הניווט.

  1. פתיחת res/navigation/nav_graph.xml. לוחצים על הכרטיסייה Text כדי לראות את קוד ה-XML של תרשים הניווט.
  2. בתוך האלמנט <fragment> של קטע הפרטים, מוסיפים את האלמנט <argument> שמוצג למטה. הארגומנט הזה, שנקרא selectedProperty, הוא מסוג MarsProperty.
<argument
   android:name="selectedProperty"
   app:argType="com.example.android.marsrealestate.network.MarsProperty"
   />
  1. קומפילציה של האפליקציה. הניווט נותן שגיאה כי MarsProperty לא ניתן להעברה. ממשק Parcelable מאפשר לבצע סריאליזציה של אובייקטים, כך שניתן להעביר את נתוני האובייקטים בין פרגמנטים או פעילויות. במקרה כזה, כדי שהנתונים בתוך האובייקט MarsProperty יועברו אל קטע הפרטים באמצעות Safe Args, האובייקט MarsProperty צריך להטמיע את הממשק Parcelable. החדשות הטובות הן ש-Kotlin מספקת קיצור דרך קל להטמעה של הממשק הזה.
  2. פתיחת network/MarsProperty.kt. מוסיפים את ההערה @Parcelize להגדרת המחלקה.

    מייבאים את kotlinx.android.parcel.Parcelize כשמתבקשים. ‫

    ההערה @Parcelize משתמשת בתוספי Kotlin Android כדי להטמיע באופן אוטומטי את ה-methods בממשק Parcelable עבור המחלקה הזו. לא צריך לעשות שום דבר אחר.
@Parcelize
data class MarsProperty (
  1. משנים את הגדרת המחלקה של 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: חיבור המקטעים

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

  1. פתיחת 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()
   }
})
  1. פתיחת detail/DetailFragment.kt. מוסיפים את השורה הזו ממש מתחת לקריאה ל-setLifecycleOwner() בשיטה onCreateView(). בשורה הזו מתקבל אובייקט MarsProperty שנבחר מ-Safe Args.

    שימו לב לשימוש באופרטור של Kotlin לאישור שערך הוא לא null ‏ (!!). אם האובייקט selectedProperty לא קיים, משהו נורא קרה ואתם רוצים שהקוד יחזיר מצביע null. (בקוד ייצור, צריך לטפל בשגיאה הזו בצורה כלשהי).
 val marsProperty = DetailFragmentArgs.fromBundle(arguments!!).selectedProperty
  1. מוסיפים את השורה הזו כדי לקבל DetailViewModelFactory חדש. תשתמשו ב-DetailViewModelFactory כדי לקבל מופע של DetailViewModel. אפליקציית המתחילים כוללת הטמעה של DetailViewModelFactory, כך שכל מה שצריך לעשות כאן הוא לאתחל אותה.
val viewModelFactory = DetailViewModelFactory(marsProperty, application)
  1. לבסוף, מוסיפים את השורה הזו כדי לקבל DetailViewModel מהמפעל ולחבר את כל החלקים.
      binding.viewModel = ViewModelProviders.of(
                this, viewModelFactory).get(DetailViewModel::class.java)
  1. קומפילציה והרצה של האפליקציה, והקשה על תמונה של נכס של Mars. יוצג קטע הפרטים של המאפיין. מקישים על לחצן 'הקודם' כדי לחזור לדף הסקירה הכללית, ורואים שמסך הפרטים עדיין די דליל. במשימה הבאה מסיימים להוסיף את נתוני הנכס לדף הפרטים.

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

  1. פתיחת 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>
  1. פתיחת detail/DetailViewModel.kt. בתחתית הכיתה, מוסיפים את הקוד שמופיע למטה.

    מייבאים את androidx.lifecycle.Transformations אם מתבקשים.

    הטרנספורמציה הזו בודקת אם הנכס שנבחר הוא נכס להשכרה, באמצעות אותה בדיקה מהמשימה הראשונה. אם הנכס הוא נכס להשכרה, הטרנספורמציה בוחרת את המחרוזת המתאימה מהמשאבים באמצעות מתג Kotlin when {}. לשני המחרוזות האלה צריך להוסיף מספר בסוף, ולכן צריך לשרשר את 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)
}
  1. מייבאים את המחלקה R שנוצרה כדי לקבל גישה למשאבי המחרוזות בפרויקט.
import com.example.android.marsrealestate.R
  1. אחרי 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
                   }))
}
  1. פתיחת 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" />
  1. קומפלו את האפליקציה והריצו אותה. עכשיו כל נתוני הנכס מופיעים בדף הפרטים, בפורמט מסודר.

פרויקט Android Studio: ‏ MarsRealEstateFinal

ביטויים של קישור

  • אפשר להשתמש בביטויי קישור בקובצי פריסה של XML כדי לבצע פעולות תכנות פשוטות, כמו חישובים מתמטיים או בדיקות מותנות, על נתונים מקושרים.
  • כדי להפנות למחלקות בתוך קובץ הפריסה, משתמשים בתג <import> בתוך התג <data>.

אפשרויות לשאילתות של שירותי אינטרנט

  • בקשות לשירותי אינטרנט יכולות לכלול פרמטרים אופציונליים.
  • כדי לציין פרמטרים של שאילתה בבקשה, משתמשים באנוטציה @Query ב-Retrofit.

קורס ב-Udacity:

מסמכי תיעוד למפתחי Android:

אחר:

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

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

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

אם אתם עובדים על ה-codelab הזה לבד, אתם יכולים להשתמש במשימות האלה כדי לבדוק את הידע שלכם.

עונים על השאלות הבאות

שאלה 1

מה עושה התג <import> בקובץ פריסה בפורמט XML?

‫▢ Include one layout file in another.

‫▢ הטמעת קוד Kotlin בקובץ הפריסה.

‫▢ מתן גישה לנכסים שמקושרים לנתונים.

‫▢ מאפשרות לכם להפנות לכיתות ולתלמידים בכיתות בביטויי קשירה.

שאלה 2

איך מוסיפים אפשרות שאילתה לקריאה לשירות אינטרנט REST ב-Retrofit?

‫▢ צירוף השאילתה לסוף כתובת ה-URL של הבקשה.

‫▢ מוסיפים פרמטר לשאילתה לפונקציה שיוצרת את הבקשה, ומציינים את הפרמטר באמצעות @Query.

‫▢ משתמשים במחלקה Query כדי ליצור בקשה.

‫▢ משתמשים בשיטת addQuery() ב-Retrofit builder.

לשיעור הבא: 9.1: מאגר

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