Android Kotlin Fundamentals 08.3 การกรองและดูรายละเอียดด้วยข้อมูลอินเทอร์เน็ต

Codelab นี้เป็นส่วนหนึ่งของหลักสูตรพื้นฐานเกี่ยวกับ Kotlin ใน Android คุณจะได้รับประโยชน์สูงสุดจากหลักสูตรนี้ หากทํางานผ่าน Codelab ตามลําดับ Codelab ของหลักสูตรทั้งหมดจะแสดงอยู่ในหน้า Landing Page ของ Codelab ของ Android Kotlin Fundamentals

บทนำ

ใน Codelab ก่อนหน้าของบทเรียนนี้ คุณได้ดูวิธีรับข้อมูลอสังหาริมทรัพย์บนดาวอังคารจากบริการทางเว็บ รวมทั้งวิธีสร้าง RecyclerView ด้วยเลย์เอาต์แบบตารางกริดเพื่อโหลดและแสดงรูปภาพจากข้อมูลดังกล่าว ใน Codelab นี้ คุณทําแอป MarsRealEstate ให้เสร็จโดยนําความสามารถในการกรองพร็อพเพอร์ตี้ Mars มาใช้งานโดยให้เช่าหรือซื้อ นอกจากนี้ คุณยังสร้างมุมมองรายละเอียดได้ด้วย หากผู้ใช้แตะรูปภาพพร็อพเพอร์ตี้ในภาพรวม ผู้ใช้จะเห็นมุมมองรายละเอียดที่มีรายละเอียดเกี่ยวกับพร็อพเพอร์ตี้นั้น

สิ่งที่ควรทราบอยู่แล้ว

  • วิธีสร้างและใช้ส่วนย่อย
  • วิธีไปยังส่วนต่างๆ และ Fragment (ปลั๊กอิน Gradle) เพื่อส่งข้อมูลระหว่างส่วนย่อย
  • วิธีใช้คอมโพเนนต์สถาปัตยกรรมรวมถึงโมเดลมุมมอง ดูโรงงานโมเดล การเปลี่ยนรูปแบบ และ LiveData
  • วิธีเรียกข้อมูลที่เข้ารหัส JSON จากบริการเว็บ REST และแยกวิเคราะห์ข้อมูลดังกล่าวลงในออบเจ็กต์ Kotlin ด้วยไลบรารี Retrofit และ Moshi

สิ่งที่จะได้เรียนรู้

  • วิธีใช้นิพจน์การเชื่อมโยงที่ซับซ้อนในไฟล์เลย์เอาต์
  • วิธีส่งคําขอ Retrofit ไปยังบริการเว็บด้วยตัวเลือกการค้นหา

สิ่งที่คุณจะทํา

  • แก้ไขแอป MarsRealEstate เพื่อทําเครื่องหมายคุณสมบัติของดาวอังคารที่ขาย (เทียบกับวัตถุที่ให้เช่า) ด้วยไอคอนเครื่องหมายดอลลาร์
  • ใช้เมนูตัวเลือกในหน้าภาพรวมเพื่อสร้างคําขอบริการเว็บที่กรองพร็อพเพอร์ตี้ดาวอังคารตามประเภท
  • สร้างส่วนย่อยแบบละเอียดสําหรับพร็อพเพอร์ตี้ดาวอังคาร แล้วเชื่อมโยงส่วนย่อยนั้นไปยังตารางกริดภาพรวมด้วยการนําทาง และส่งผ่านข้อมูลพร็อพเพอร์ตี้ไปยังส่วนย่อยนั้น

ใน Codelab (และ Codelab ที่เกี่ยวข้อง) คุณใช้งานแอปที่ชื่อว่า MarsRealEstate ซึ่งแสดงพร็อพเพอร์ตี้สําหรับขายบนดาวอังคาร แอปนี้เชื่อมต่อกับเซิร์ฟเวอร์อินเทอร์เน็ตเพื่อดึงข้อมูลและแสดงข้อมูลพร็อพเพอร์ตี้ รวมถึงรายละเอียดต่างๆ เช่น ราคา และพร็อพเพอร์ตี้พร้อมให้ขายหรือให้เช่า รูปภาพที่เป็นตัวแทนของแต่ละสถานที่คือรูปภาพในชีวิตจริงจากดาวอังคารที่ถ่ายจากหุ่นยนต์สํารวจดาวอังคารของ NASA&#39 ใน Codelab ก่อนหน้า ให้คุณสร้าง 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"
},

ในงานนี้ คุณจะเริ่มทํางานกับพร็อพเพอร์ตี้ประเภทดาวอังคารเพื่อเพิ่มรูปภาพสัญลักษณ์ดอลลาร์ลงในพร็อพเพอร์ตี้ในหน้าภาพรวมที่ขาย

ขั้นตอนที่ 1: อัปเดต MarsProperty ให้รวมประเภทดังกล่าว

คลาส MarsProperty จะกําหนดโครงสร้างข้อมูลของพร็อพเพอร์ตี้แต่ละรายการที่ให้บริการโดยเว็บ ใน Codelab ก่อนหน้า คุณใช้ไลบรารี Moshi เพื่อแยกวิเคราะห์การตอบกลับ JSON ดิบจากบริการเว็บ Mars ไปยังออบเจ็กต์ข้อมูล MarsProperty แต่ละรายการ

ในขั้นตอนนี้ คุณจะต้องเพิ่มตรรกะในคลาส MarsProperty เพื่อระบุว่าพร็อพเพอร์ตี้นั้นให้เช่าหรือไม่ (กล่าวคือ เป็นสตริง "rent" หรือ "buy") โดยคุณจะใช้ตรรกะนี้ในที่มากกว่า 1 แห่ง จึงควรมีไว้ที่นี่ในชั้นเรียนข้อมูลมากกว่าการทําซ้ํา

  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> รายการที่ 2 ด้านล่างองค์ประกอบแรกภายใน FrameLayout โปรดใช้คําจํากัดความที่แสดงด้านล่าง รูปภาพนี้จะปรากฏในมุมขวาล่างของรายการตารางกริด ที่ด้านบนของรูปภาพดาวอังคาร และใช้รูปที่วาดได้ตามที่กําหนดไว้ใน 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 ในกรณีนี้ คุณจะต้องใช้โอเปอเรเตอร์สามส่วน (?:) เพื่อทําการทดสอบ (ออบเจ็กต์นี้เป็นอุปกรณ์เช่า) คุณระบุผลลัพธ์จริง 1 รายการ (ซ่อนไอคอนเครื่องหมายดอลลาร์ด้วย View.GONE) และอีกรายการหนึ่งสําหรับเท็จ (แสดงไอคอนนั้นด้วย 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 ทั้งหมดในตารางภาพรวม หากผู้ใช้กําลังเลือกซื้อที่พักให้เช่าบนดาวอังคาร การมีไอคอนซึ่งระบุที่พักที่พร้อมขายจะเป็นประโยชน์ แต่ก็ยังมีที่พักอื่นๆ จํานวนมากที่ต้องเลื่อนดู ในงานนี้ คุณจะได้เพิ่มเมนูตัวเลือกลงใน Fragment ภาพรวมที่อนุญาตให้ผู้ใช้แสดงเฉพาะบริการเช่าวิดีโอ เฉพาะพร็อพเพอร์ตี้ที่ประกาศขาย หรือแสดงทั้งหมด

วิธีหนึ่งที่คุณทําได้คือการทดสอบประเภทของ MarsProperty แต่ละรายการในภาพรวมและแสดงเฉพาะพร็อพเพอร์ตี้ที่ตรงกัน แต่บริการเว็บ Mars จริงจะมีพารามิเตอร์หรือตัวเลือกการค้นหา (เรียกว่า filter) ซึ่งช่วยให้คุณได้รับพร็อพเพอร์ตี้ประเภท rent หรือประเภท buy เท่านั้น คุณสามารถใช้การค้นหาตัวกรองนี้กับ URL ของบริการเว็บ realestate ได้ในเบราว์เซอร์ดังนี้

https://android-kotlin-fun-mars-server.appspot.com/realestate?filter=buy

ในงานนี้ คุณจะได้แก้ไขคลาส MarsApiService เพื่อเพิ่มตัวเลือกการค้นหาไปยังคําขอบริการเว็บด้วย Retrofit จากนั้นเปิดเมนูตัวเลือกเพื่อดาวน์โหลดข้อมูลพร็อพเพอร์ตี้ดาวอังคารทั้งหมดอีกครั้งโดยใช้ตัวเลือกการค้นหานั้น เนื่องจากคําตอบที่ได้รับจากบริการเว็บมีเฉพาะพร็อพเพอร์ตี้ที่คุณสนใจเท่านั้น จึงไม่จําเป็นต้องเปลี่ยนตรรกะการแสดงผลข้อมูลพร็อพเพอร์ตี้ของตารางกริดภาพรวมเลย

ขั้นตอนที่ 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() (ซึ่งก็คือ "การเพิกถอน") เพื่อสร้างคําขอบริการเว็บที่มีตัวเลือกตัวกรอง ทุกครั้งที่มีการเรียก getProperties() URL คําขอจะมีส่วน ?filter=type ซึ่งสั่งให้บริการเว็บตอบกลับด้วยผลลัพธ์ที่ตรงกับคําค้นหานั้น
fun getProperties(@Query("filter") type: String):  

ขั้นตอนที่ 2: อัปเดตโมเดลมุมมองภาพรวม

คุณขอข้อมูลจาก MarsApiService ในเมธอด getMarsRealEstateProperties() ใน OverviewViewModel ตอนนี้คุณต้องอัปเดตคําขอดังกล่าวเพื่อรับอาร์กิวเมนต์ตัวกรอง

  1. เปิด overview/OverviewViewModel.kt คุณจะเห็นข้อผิดพลาดใน Android Studio เนื่องจากการเปลี่ยนแปลงที่คุณทําในขั้นตอนก่อนหน้า เพิ่ม MarsApiFilter (การแจกแจงค่าตัวกรองที่เป็นไปได้) เป็นพารามิเตอร์ในการเรียก 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. ในตอนท้ายของชั้นเรียน ให้เพิ่มเมธอด updateFilter() ที่มีอาร์กิวเมนต์ MarsApiFilter และเรียก getMarsRealEstateProperties() ด้วยอาร์กิวเมนต์นั้น
fun updateFilter(filter: MarsApiFilter) {
   getMarsRealEstateProperties(filter)
}

ขั้นตอนที่ 3: เชื่อมต่อส่วนย่อยกับเมนูตัวเลือก

ขั้นตอนสุดท้ายคือการเชื่อมต่อเมนูรายการเพิ่มเติมกับส่วนย่อยเพื่อเรียก updateFilter() ในโมเดลข้อมูลพร็อพเพอร์ตี้เมื่อผู้ใช้เลือกตัวเลือกเมนู

  1. เปิด res/menu/overflow_menu.xml แอป MarsRealEstate มีเมนูรายการเพิ่มเติมอยู่แล้วซึ่งมี 3 ตัวเลือก ได้แก่ การแสดงพร็อพเพอร์ตี้ทั้งหมด แสดงเฉพาะบริการเช่า และแสดงเฉพาะพร็อพเพอร์ตี้ที่ประกาศขาย
<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() ในโมเดลข้อมูลพร็อพเพอร์ตี้ด้วยตัวกรองที่เหมาะสม ใช้บล็อก Kotlin when {} เพื่อสลับระหว่างตัวเลือก ใช้ 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. เลือกซื้อจากเมนูตัวเลือก พร็อพเพอร์ตี้จะโหลดซ้ําอีกครั้ง และพร็อพเพอร์ตี้ทั้งหมดจะปรากฏพร้อมกับไอคอนดอลลาร์ (แสดงเฉพาะพร็อพเพอร์ตี้ที่ประกาศขาย)

ตอนนี้คุณจะมีตารางไอคอนแบบเลื่อนสําหรับพร็อพเพอร์ตี้ดาวอังคารแล้ว แต่ก็ถึงเวลาดูรายละเอียดเพิ่มเติมแล้ว ในงานนี้ คุณจะได้เพิ่มส่วนหนึ่งของรายละเอียดเพื่อแสดงรายละเอียดของพร็อพเพอร์ตี้นั้น ส่วนย่อยรายละเอียดจะแสดงรูปภาพขนาดใหญ่ ราคา และประเภทที่พัก ไม่ว่าจะเป็นบริการให้เช่าหรือขาย

ส่วนย่อยนี้จะเปิดขึ้นเมื่อผู้ใช้แตะรูปภาพในตารางภาพรวม หากต้องการทําเช่นนี้ คุณต้องเพิ่ม Listener onClick ไปยังรายการในตาราง RecyclerView แล้วไปยังส่วนใหม่ คุณไปยังส่วนต่างๆ ได้โดยทําให้เกิดการเปลี่ยนแปลง LiveData ใน ViewModel เช่นเดียวกับที่เรียนรู้ไปในบทเรียนเหล่านี้ นอกจากนี้ คุณยังใช้ปลั๊กอินการนําทางของ Fragment เพื่อความปลอดภัยของ Fragment เพื่อส่งข้อมูล MarsProperty ที่เลือกจาก Fragment ภาพรวมไปยัง Fragment รายละเอียดได้ด้วย

ขั้นตอนที่ 1: สร้างโมเดลมุมมองรายละเอียดและอัปเดตเลย์เอาต์รายละเอียด

ตอนนี้คุณต้องใช้โมเดลมุมมองและไฟล์เลย์เอาต์สําหรับส่วนย่อยรายละเอียด เช่นเดียวกับกระบวนการที่คุณใช้กับโมเดลมุมมองภาพรวมและส่วนย่อย

  1. เปิด detail/DetailViewModel.kt เหมือนกับโฟลเดอร์ Kotlin ที่เกี่ยวข้องกับเครือข่ายอยู่ในโฟลเดอร์ network และไฟล์ภาพรวมใน overview โฟลเดอร์ detail จะมีไฟล์ที่เกี่ยวข้องกับมุมมองรายละเอียด โปรดสังเกตว่าคลาส DetailViewModel (ว่างเปล่าอยู่ในขณะนี้) ใช้ marsProperty เป็นพารามิเตอร์ในเครื่องมือสร้าง
class DetailViewModel( marsProperty: MarsProperty,
                     app: Application) : AndroidViewModel(app) {
}
  1. ในคําจํากัดความชั้นเรียน ให้เพิ่ม LiveData สําหรับพร็อพเพอร์ตี้ Mars ที่เลือกไว้ เพื่อให้ระบบแสดงข้อมูลนั้นในมุมมองรายละเอียด ทําตามรูปแบบปกติของการสร้าง 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. ไปที่แท็บข้อความสําหรับเลย์เอาต์ ที่ด้านบนของเลย์เอาต์ ให้เพิ่มองค์ประกอบ <data> เพื่อเชื่อมโยงโมเดลมุมมองรายละเอียดกับเลย์เอาต์ ก่อนองค์ประกอบ <ScrollView>
<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 เปลี่ยนเป็น "ไม่มี" ระบบจะทริกเกอร์การนําทาง (ในเร็วๆ นี้ คุณจะเพิ่มโค้ดเพื่อสังเกตตัวแปรนี้และเรียกใช้การนําทาง)
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: ตั้งค่า Listener การคลิกในอะแดปเตอร์และส่วนย่อยของตารางกริด

  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() กําหนด Listener การคลิกระหว่างการเรียกไปยัง 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 แยกวิเคราะห์ได้

เมื่อผู้ใช้แตะรูปภาพในตารางกริดภาพรวม แอปควรไปที่ส่วนรายละเอียดและผ่านรายละเอียดของพร็อพเพอร์ตี้ดาวอังคารที่เลือกเพื่อให้มุมมองรายละเอียดแสดงข้อมูลนั้นได้

ขณะนี้คุณมี Listener การคลิกจาก PhotoGridAdapter เพื่อจัดการการแตะ และวิธีทริกเกอร์การนําทางจากโมเดลข้อมูลพร็อพเพอร์ตี้แล้ว แต่ยังไม่มีออบเจ็กต์ MarsProperty ที่ส่งไปยัง Fragment รายละเอียด สําหรับคุณได้ใช้ Safe Args จากคอมโพเนนต์การนําทาง

  1. เปิด res/navigation/nav_graph.xml คลิกแท็บข้อความเพื่อดูโค้ด XML ของกราฟการนําทาง
  2. ภายในองค์ประกอบ <fragment> สําหรับส่วนย่อยรายละเอียด ให้เพิ่มองค์ประกอบ <argument> ที่แสดงด้านล่าง อาร์กิวเมนต์นี้ชื่อ selectedProperty เป็นประเภท MarsProperty
<argument
   android:name="selectedProperty"
   app:argType="com.example.android.marsrealestate.network.MarsProperty"
   />
  1. คอมไพล์แอป การนําทางมีข้อผิดพลาดให้คุณเนื่องจากMarsPropertyไม่พาร์เซล อินเทอร์เฟซ Parcelable ช่วยให้เรียงอันดับออบเจ็กต์เพื่อให้ส่งผ่านข้อมูลได้ระหว่างส่วนย่อยหรือกิจกรรม ในกรณีนี้ MarsProperty ต้องใช้อินเทอร์เฟซ Parcelable เพื่อให้ข้อมูลภายในออบเจ็กต์ MarsProperty ส่งไปยังส่วนย่อยรายละเอียดผ่าน Safe Args ข่าวดีคือ Kotlin มีทางลัดง่ายๆ สําหรับการใช้งานอินเทอร์เฟซดังกล่าว
  2. เปิด network/MarsProperty.kt เพิ่มคําอธิบายประกอบ @Parcelize ลงในคําจํากัดความชั้นเรียน

    นําเข้า kotlinx.android.parcel.Parcelize เมื่อมีการร้องขอ

    คําอธิบายประกอบ @Parcelize ใช้ส่วนขยาย Kotlin สําหรับ Android เพื่อใช้วิธีการโดยอัตโนมัติในอินเทอร์เฟซ 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 ตามคําขอ

    ผู้สังเกตการณ์จะทดสอบว่า MarsProperty it ในแลมบ์ดาไม่ใช่ 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 ที่เลือกจาก Google Safe Browsing

    โปรดสังเกตการใช้โอเปอเรเตอร์การยืนยัน Kotlin' (!!) หากไม่พบ 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 ยังมีประเภทที่พัก (เช่าหรือซื้อ) และราคาที่พักด้วย หน้าจอรายละเอียดควรมีทั้ง 2 ค่านี้และจะเป็นประโยชน์หากที่พักให้เช่าระบุว่าราคาเป็นค่ารายเดือน คุณมีการเปลี่ยนรูปแบบ LiveData ในโมเดลข้อมูลพร็อพเพอร์ตี้เพื่อนําทั้ง 2 อย่างนี้มาใช้

  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 {} ทั้ง 2 สตริงนี้ต้องประกอบด้วยตัวเลขในตอนท้าย เพื่อให้คุณต่อ 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 ใช้ทําอะไรได้บ้าง

▢ จะรวมไฟล์เลย์เอาต์ 1 ไฟล์ไว้ในไฟล์อื่น

▢ ฝังโค้ด Kotlin ภายในไฟล์เลย์เอาต์

▢ ให้การเข้าถึงพร็อพเพอร์ตี้ที่ผูกข้อมูล

▢ ช่วยให้คุณสามารถอ้างอิงชั้นเรียนและสมาชิกในชั้นเรียนในนิพจน์การเชื่อมโยง

คำถามที่ 2

คุณจะเพิ่มตัวเลือกการค้นหาไปยังการเรียกใช้บริการเว็บ REST ใน Retrofit ได้อย่างไร

▢ ต่อท้ายการค้นหาต่อท้าย URL คําขอ

▢ เพิ่มพารามิเตอร์สําหรับการค้นหาไปยังฟังก์ชันที่ส่งคําขอ และใส่คําอธิบายประกอบในพารามิเตอร์นั้นด้วย @Query

▢ ใช้คลาส Query เพื่อสร้างคําขอ

▢ ใช้วิธีการ addQuery() ในเครื่องมือสร้าง Retrofit

เริ่มบทเรียนถัดไป: 9.1: ที่เก็บ

สําหรับลิงก์ไปยัง Codelab อื่นๆ ในหลักสูตรนี้ โปรดดูหน้า Landing Page ของ Codelab ของ Android Kotlin Fundamentals