Codelab นี้เป็นส่วนหนึ่งของหลักสูตรหลักพื้นฐานของ Android Kotlin คุณจะได้รับประโยชน์สูงสุดจากหลักสูตรนี้หากทำตาม Codelab ตามลำดับ Codelab ของหลักสูตรทั้งหมดแสดงอยู่ในหน้า Landing Page ของ Codelab หลักพื้นฐานของ Android Kotlin
บทนำ
ใน Codelab ก่อนหน้าของบทเรียนนี้ คุณได้เรียนรู้วิธีรับข้อมูลเกี่ยวกับอสังหาริมทรัพย์บนดาวอังคารจากบริการบนเว็บ และวิธีสร้าง RecyclerView ที่มีเลย์เอาต์ตารางกริดเพื่อโหลดและแสดงรูปภาพจากข้อมูลดังกล่าว ในโค้ดแล็บนี้ คุณจะสร้างแอป MarsRealEstate ให้เสร็จสมบูรณ์โดยการใช้ความสามารถในการกรองอสังหาริมทรัพย์ในดาวอังคารตามสถานะว่าพร้อมให้เช่าหรือซื้อ นอกจากนี้ คุณยังสร้างมุมมองแบบละเอียดเพื่อให้ผู้ใช้เห็นมุมมองแบบละเอียดพร้อมรายละเอียดเกี่ยวกับที่พักนั้นหากแตะรูปภาพที่พักในภาพรวม
สิ่งที่คุณควรทราบอยู่แล้ว
- วิธีสร้างและใช้ Fragment
- วิธีไปยังส่วนต่างๆ ระหว่าง Fragment และใช้ Safe Args (ปลั๊กอิน Gradle) เพื่อส่งข้อมูลระหว่าง Fragment
- วิธีใช้คอมโพเนนต์สถาปัตยกรรม รวมถึงโมเดลมุมมอง โรงงานโมเดลมุมมอง การแปลง และ
LiveData - วิธีดึงข้อมูลที่เข้ารหัส JSON จากเว็บเซอร์วิส REST และแยกวิเคราะห์ข้อมูลนั้นเป็นออบเจ็กต์ Kotlin ด้วยไลบรารี Retrofit และ Moshi
สิ่งที่คุณจะได้เรียนรู้
- วิธีใช้การแสดงผลการเชื่อมโยงที่ซับซ้อนในไฟล์เลย์เอาต์
- วิธีส่งคำขอ Retrofit ไปยังบริการเว็บด้วยตัวเลือกการค้นหา
สิ่งที่คุณต้องทำ
- แก้ไขแอป MarsRealEstate เพื่อทำเครื่องหมายที่พักในดาวอังคารที่ขาย (เทียบกับที่พักที่ให้เช่า) ด้วยไอคอนดอลลาร์
- ใช้เมนูตัวเลือกในหน้าภาพรวมเพื่อสร้างคำขอบริการเว็บที่กรองพร็อพเพอร์ตี้ของ Mars ตามประเภท
- สร้างส่วนย่อยรายละเอียดสำหรับพร็อพเพอร์ตี้ Mars เชื่อมต่อส่วนย่อยนั้นกับตารางภาพรวมด้วยการนำทาง และส่งข้อมูลพร็อพเพอร์ตี้ไปยังส่วนย่อยนั้น
ในโค้ดแล็บนี้ (และโค้ดแล็บที่เกี่ยวข้อง) คุณจะได้ทำงานกับแอปชื่อ MarsRealEstate ซึ่งแสดงอสังหาริมทรัพย์ที่ขายบนดาวอังคาร แอปนี้เชื่อมต่อกับเซิร์ฟเวอร์อินเทอร์เน็ตเพื่อดึงและแสดงข้อมูลพร็อพเพอร์ตี้ รวมถึงรายละเอียดต่างๆ เช่น ราคา และสถานะของพร็อพเพอร์ตี้ว่าพร้อมขายหรือให้เช่าหรือไม่ รูปภาพที่แสดงถึงที่พักแต่ละแห่งเป็นรูปภาพจากดาวอังคารที่ถ่ายจากยานสำรวจดาวอังคารของ NASA ใน 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") คุณจะใช้ตรรกะนี้ในหลายที่ ดังนั้นจึงควรมีตรรกะนี้ในคลาสข้อมูลมากกว่าที่จะทำซ้ำ
- เปิดแอป MarsRealEstate จากโค้ดแล็บล่าสุด (คุณดาวน์โหลด MarsRealEstateGrid ได้หากยังไม่มีแอป)
- เปิด
network/MarsProperty.ktเพิ่มเนื้อหาลงในคำจำกัดความของคลาสMarsPropertyและเพิ่มตัวรับที่กำหนดเองสำหรับ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: อัปเดตเลย์เอาต์ของรายการในตารางกริด
ตอนนี้คุณอัปเดตเลย์เอาต์ของรายการสำหรับตารางกริดของรูปภาพเพื่อแสดง Drawable เครื่องหมายดอลลาร์เฉพาะในรูปภาพของที่พักที่ขายเท่านั้นได้แล้ว

ด้วยนิพจน์การเชื่อมโยงข้อมูล คุณจะทำการทดสอบนี้ในเลย์เอาต์ XML สำหรับรายการกริดได้ทั้งหมด
- เปิด
res/layout/grid_view_item.xmlนี่คือไฟล์เลย์เอาต์สำหรับแต่ละเซลล์ในเลย์เอาต์ตารางกริดสำหรับRecyclerViewปัจจุบันไฟล์มีเฉพาะองค์ประกอบ<ImageView>สำหรับรูปภาพพร็อพเพอร์ตี้ - เพิ่มองค์ประกอบ
<import>สำหรับคลาสViewในองค์ประกอบ<data>คุณใช้การนำเข้าเมื่อต้องการใช้คอมโพเนนต์ของคลาสภายในนิพจน์การเชื่อมโยงข้อมูลในไฟล์เลย์เอาต์ ในกรณีนี้ คุณจะใช้ค่าคงที่View.GONEและView.VISIBLEดังนั้นคุณจึงต้องมีสิทธิ์เข้าถึงคลาสView
<import type="android.view.View"/>- ล้อมรอบมุมมองรูปภาพทั้งหมดด้วย
FrameLayoutเพื่อให้วาง Drawable เครื่องหมายดอลลาร์ซ้อนทับบนรูปภาพพร็อพเพอร์ตี้ได้
<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>ที่ 2 ไว้ใต้องค์ประกอบแรกภายใน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 ในกรณีนี้ คุณใช้ตัวดำเนินการแบบมี 3 นิพจน์ (?:) เพื่อทำการทดสอบ (ออบเจ็กต์นี้เป็นรายการเช่าใช่ไหม) คุณระบุผลลัพธ์หนึ่งสำหรับค่าจริง (ซ่อนไอคอนเครื่องหมายดอลลาร์ด้วย 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>- คอมไพล์และเรียกใช้แอป แล้วสังเกตว่าที่พักที่ไม่ใช่ที่พักให้เช่าจะมีไอคอนเครื่องหมายดอลลาร์

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

วิธีหนึ่งในการทํางานนี้คือการทดสอบประเภทสําหรับแต่ละ MarsProperty ในตารางกริดภาพรวม และแสดงเฉพาะพร็อพเพอร์ตี้ที่ตรงกัน อย่างไรก็ตาม เว็บบริการของดาวอังคารมีพารามิเตอร์หรือตัวเลือกการค้นหา (เรียกว่า filter) ที่ช่วยให้คุณรับเฉพาะพร็อพเพอร์ตี้ประเภท rent หรือประเภท buy ได้ คุณใช้การค้นหาตัวกรองนี้กับ URL ของบริการเว็บ realestate ในเบราว์เซอร์ได้ดังนี้
https://android-kotlin-fun-mars-server.appspot.com/realestate?filter=buyในงานนี้ คุณจะแก้ไขคลาส MarsApiService เพื่อเพิ่มตัวเลือกการค้นหาลงในคำขอของบริการเว็บด้วย Retrofit จากนั้นเชื่อมต่อเมนูตัวเลือกเพื่อดาวน์โหลดข้อมูลพร็อพเพอร์ตี้ทั้งหมดของดาวอังคารอีกครั้งโดยใช้ตัวเลือกการค้นหานั้น เนื่องจากการตอบกลับที่คุณได้รับจากบริการเว็บมีเฉพาะพร็อพเพอร์ตี้ที่คุณสนใจ คุณจึงไม่จำเป็นต้องเปลี่ยนตรรกะการแสดงผลของมุมมองสำหรับตารางภาพรวมเลย
ขั้นตอนที่ 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(การแจงนับค่าตัวกรองที่เป็นไปได้) เป็นพารามิเตอร์ในการเรียก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)
}- ที่ท้ายคลาส ให้เพิ่มเมธอด
updateFilter()ที่รับอาร์กิวเมนต์MarsApiFilterและเรียกใช้getMarsRealEstateProperties()ด้วยอาร์กิวเมนต์นั้น
fun updateFilter(filter: MarsApiFilter) {
getMarsRealEstateProperties(filter)
}ขั้นตอนที่ 3: เชื่อมต่อ Fragment กับเมนูตัวเลือก
ขั้นตอนสุดท้ายคือการเชื่อมต่อเมนูรายการเพิ่มเติมกับ Fragment เพื่อเรียกใช้ updateFilter() ใน ViewModel เมื่อผู้ใช้เลือกตัวเลือกเมนู
- เปิด
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>- เปิด
overview/OverviewFragment.ktเมื่อสิ้นสุดคลาส ให้ใช้เมธอดonOptionsItemSelected()เพื่อจัดการการเลือกรายการในเมนู
override fun onOptionsItemSelected(item: MenuItem): Boolean {
} - ใน
onOptionsItemSelected()ให้เรียกใช้เมธอดupdateFilter()ใน ViewModel ด้วยตัวกรองที่เหมาะสม ใช้บล็อก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 แต่ถึงเวลาดูรายละเอียดเพิ่มเติมแล้ว ในงานนี้ คุณจะเพิ่ม Detail Fragment เพื่อแสดงรายละเอียดของพร็อพเพอร์ตี้ที่เฉพาะเจาะจง โดยรายละเอียดจะแสดงรูปภาพที่ใหญ่ขึ้น ราคา และประเภทที่พัก ไม่ว่าจะเป็นที่พักให้เช่าหรือที่พักสำหรับขาย

ระบบจะเปิดใช้ Fragment นี้เมื่อผู้ใช้แตะรูปภาพในตารางภาพรวม หากต้องการดำเนินการนี้ คุณต้องเพิ่มonClick Listener ไปยังRecyclerViewรายการตารางกริด แล้วไปยัง Fragment ใหม่ คุณจะไปยังส่วนต่างๆ ได้โดยการทริกเกอร์การเปลี่ยนแปลง LiveData ใน ViewModel ดังที่คุณได้ทำตลอดบทเรียนเหล่านี้ นอกจากนี้ คุณยังใช้ปลั๊กอิน Safe Args ของคอมโพเนนต์การนำทางเพื่อส่งMarsPropertyข้อมูลที่เลือกจาก Fragment ภาพรวมไปยัง Fragment รายละเอียดได้ด้วย
ขั้นตอนที่ 1: สร้างโมเดลมุมมองรายละเอียดและอัปเดตเลย์เอาต์รายละเอียด
เช่นเดียวกับกระบวนการที่คุณใช้สำหรับโมเดลมุมมองภาพรวมและ Fragment ตอนนี้คุณต้องใช้โมเดลมุมมองและไฟล์เลย์เอาต์สำหรับ Fragment รายละเอียด
- เปิด
detail/DetailViewModel.ktเช่นเดียวกับไฟล์ Kotlin ที่เกี่ยวข้องกับเครือข่ายซึ่งอยู่ในโฟลเดอร์networkและไฟล์ภาพรวมในoverviewโฟลเดอร์detailจะมีไฟล์ที่เชื่อมโยงกับมุมมองรายละเอียด โปรดทราบว่าDetailViewModelclass (ว่างเปล่าในตอนนี้) จะใช้marsPropertyเป็นพารามิเตอร์ในตัวสร้าง
class DetailViewModel( marsProperty: MarsProperty,
app: Application) : AndroidViewModel(app) {
}- ภายในคำจำกัดความของคลาส ให้เพิ่ม
LiveDataสำหรับพร็อพเพอร์ตี้ Mars ที่เลือก เพื่อแสดงข้อมูลดังกล่าวในมุมมองรายละเอียด ทำตามรูปแบบปกติของการสร้าง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แล้วดูในมุมมองการออกแบบ
นี่คือไฟล์เลย์เอาต์สำหรับรายละเอียดของ Fragment โดยมีImageViewสำหรับรูปภาพขนาดใหญ่TextViewสำหรับประเภทที่พัก (เช่าหรือขาย) และTextViewสำหรับราคา โปรดสังเกตว่าเลย์เอาต์ข้อจำกัดจะอยู่ในScrollViewเพื่อให้เลื่อนโดยอัตโนมัติหากมุมมองมีขนาดใหญ่เกินไปสำหรับการแสดงผล เช่น เมื่อผู้ใช้ดูในโหมดแนวนอน - ไปที่แท็บข้อความสำหรับเลย์เอาต์ ที่ด้านบนของเลย์เอาต์ ให้เพิ่มองค์ประกอบ
<data>ก่อนองค์ประกอบ<ScrollView>เพื่อเชื่อมโยงโมเดลมุมมองรายละเอียดกับเลย์เอาต์
<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: กำหนดการนำทางใน ViewModel ของภาพรวม
เมื่อผู้ใช้แตะรูปภาพในโมเดลภาพรวม ระบบควรทริกเกอร์การนำทางไปยัง Fragment ที่แสดงรายละเอียดเกี่ยวกับรายการที่คลิก
- เปิด
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เป็น Null คุณต้องใช้ค่านี้เพื่อทำเครื่องหมายสถานะการนำทางว่าเสร็จสมบูรณ์ และเพื่อหลีกเลี่ยงไม่ให้ระบบทริกเกอร์การนำทางอีกครั้งเมื่อผู้ใช้กลับมาจากมุมมองรายละเอียด
fun displayPropertyDetailsComplete() {
_navigateToSelectedProperty.value = null
}ขั้นตอนที่ 3: ตั้งค่าเครื่องมือฟังการคลิกใน Grid Adapter และ Fragment
- เปิด
overview/PhotoGridAdapter.ktเมื่อสิ้นสุดคลาส ให้สร้างOnClickListenerคลาสที่กำหนดเองOnClickListenerซึ่งใช้ Lambda ที่มีพารามิเตอร์marsPropertyภายในคลาส ให้กำหนดฟังก์ชันonClick()ที่ตั้งค่าเป็นพารามิเตอร์ Lambda
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ใน ViewModel สำหรับการนำทาง
binding.photosGrid.adapter = PhotoGridAdapter(PhotoGridAdapter.OnClickListener {
viewModel.displayPropertyDetails(it)
})ขั้นตอนที่ 4: แก้ไขกราฟการนำทางและทำให้ MarsProperty สามารถส่งผ่านได้
เมื่อผู้ใช้แตะรูปภาพในตารางภาพรวม แอปควรไปยัง Fragment รายละเอียดและส่งรายละเอียดของพร็อพเพอร์ตี้ดาวอังคารที่เลือก เพื่อให้มุมมองรายละเอียดแสดงข้อมูลนั้นได้

ตอนนี้คุณมีเครื่องมือฟังการคลิกจาก PhotoGridAdapter เพื่อจัดการการแตะ และวิธีทริกเกอร์การนำทางจาก ViewModel แต่คุณยังไม่มีออบเจ็กต์ MarsProperty ที่ส่งไปยังรายละเอียด Fragment โดยคุณจะใช้ Safe Args จากคอมโพเนนต์การนำทาง
- เปิด
res/navigation/nav_graph.xmlคลิกแท็บข้อความเพื่อดูโค้ด XML สำหรับกราฟการนำทาง - ภายในองค์ประกอบ
<fragment>สำหรับรายละเอียดของข้อมูล ให้เพิ่มองค์ประกอบ<argument>ที่แสดงด้านล่าง อาร์กิวเมนต์นี้เรียกว่าselectedPropertyมีประเภทเป็นMarsProperty
<argument
android:name="selectedProperty"
app:argType="com.example.android.marsrealestate.network.MarsProperty"
/>- คอมไพล์แอป การนำทางจะแสดงข้อผิดพลาดเนื่องจาก
MarsPropertyไม่ใช่ parcelable อินเทอร์เฟซParcelableช่วยให้สามารถซีเรียลไลซ์ออบเจ็กต์ได้ เพื่อให้ส่งข้อมูลของออบเจ็กต์ไปมาระหว่าง Fragment หรือกิจกรรมได้ ในกรณีนี้MarsPropertyต้องใช้Parcelableอินเทอร์เฟซเพื่อให้ส่งข้อมูลภายในออบเจ็กต์MarsPropertyไปยังรายละเอียด Fragment ผ่าน Safe Args ได้ ข่าวดีคือ Kotlin มีทางลัดง่ายๆ ในการใช้ส่วนติดต่อดังกล่าว - เปิด
network/MarsProperty.ktเพิ่ม@Parcelizeคำอธิบายประกอบลงในคำจำกัดความของคลาส
นำเข้าkotlinx.android.parcel.Parcelizeเมื่อระบบขอ
คำอธิบายประกอบ@Parcelizeใช้ส่วนขยาย Kotlin Android เพื่อใช้เมธอดในอินเทอร์เฟซ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: เชื่อมต่อ Fragment
คุณยังไม่ได้ไปยังส่วนต่างๆ การไปยังส่วนต่างๆ จริงจะเกิดขึ้นใน Fragment ในขั้นตอนนี้ คุณจะเพิ่มส่วนสุดท้ายสำหรับการติดตั้งใช้งานการนำทางระหว่างภาพรวมและรายละเอียดของ Fragment
- เปิด
overview/OverviewFragment.ktในonCreateView()ใต้บรรทัดที่เริ่มต้นอแดปเตอร์ตารางกริดรูปภาพ ให้เพิ่มบรรทัดที่แสดงด้านล่างเพื่อสังเกตnavigatedToSelectedPropertyจากโมเดลมุมมองภาพรวม
นำเข้าandroidx.lifecycle.Observerและนำเข้าandroidx.navigation.fragment.findNavControllerเมื่อได้รับคำขอ
Observer จะทดสอบว่าMarsPropertyซึ่งเป็นitใน Lambda ไม่ใช่ค่าว่างหรือไม่ หากไม่ใช่ค่าว่าง Observer จะรับ Navigation Controller จาก Fragment ที่มีfindNavController()เรียกใช้displayPropertyDetailsComplete()เพื่อบอกให้ ViewModel รีเซ็ต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
โปรดสังเกตการใช้ตัวดำเนินการยืนยันว่าไม่ใช่ Null ของ Kotlin (!!) หากไม่มี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)- คอมไพล์และเรียกใช้แอป แล้วแตะรูปภาพของดาวอังคาร รายละเอียดของพร็อพเพอร์ตี้นั้นจะปรากฏขึ้น แตะปุ่มย้อนกลับเพื่อกลับไปที่หน้าภาพรวม แล้วจะเห็นว่าหน้าจอรายละเอียดก็ยังค่อนข้างว่างเปล่า คุณจะเพิ่มข้อมูลพร็อพเพอร์ตี้ลงในหน้ารายละเอียดนั้นให้เสร็จสมบูรณ์ได้ในงานถัดไป
ปัจจุบันหน้ารายละเอียดจะแสดงเฉพาะรูปภาพดาวอังคารรูปเดียวกันกับที่คุณเคยเห็นในหน้าภาพรวม คลาส MarsProperty ยังมีประเภทพร็อพเพอร์ตี้ (เช่าหรือซื้อ) และราคาพร็อพเพอร์ตี้ด้วย หน้าจอรายละเอียดควรมีทั้ง 2 ค่านี้ และจะเป็นประโยชน์หากที่พักให้เช่าระบุว่าราคาเป็นค่าต่อเดือน คุณใช้LiveDataการเปลี่ยนรูปแบบใน ViewModel เพื่อใช้ทั้ง 2 อย่าง
- เปิด
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 {}สตริงทั้ง 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)
}- นำเข้าคลาส
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
- การเชื่อมโยงอะแดปเตอร์
- การออกแบบและการเชื่อมโยงนิพจน์
- การไปยังส่วนต่างๆ
- เริ่มต้นใช้งานคอมโพเนนต์การนำทาง
- ส่งข้อมูลระหว่างปลายทาง (อธิบาย Safe Args ด้วย)
Transformationsชั้นเรียนViewModelProviderชั้นเรียนViewModelProvider.Factoryชั้นเรียน
อื่นๆ:
- Glide
- Retrofit Query class
- ส่วนขยายตัวสร้าง Parcelable ของ Kotlin
ส่วนนี้แสดงรายการการบ้านที่เป็นไปได้สำหรับนักเรียน/นักศึกษาที่กำลังทำ Codelab นี้เป็นส่วนหนึ่งของหลักสูตรที่สอนโดยผู้สอน ผู้สอนมีหน้าที่ดำเนินการต่อไปนี้
- มอบหมายการบ้านหากจำเป็น
- สื่อสารกับนักเรียนเกี่ยวกับวิธีส่งงานที่ได้รับมอบหมาย
- ให้คะแนนงานการบ้าน
ผู้สอนสามารถใช้คำแนะนำเหล่านี้ได้มากน้อยตามที่ต้องการ และควรมีอิสระในการมอบหมายการบ้านอื่นๆ ที่เห็นว่าเหมาะสม
หากคุณกำลังทำ Codelab นี้ด้วยตนเอง โปรดใช้แบบฝึกหัดเหล่านี้เพื่อทดสอบความรู้ของคุณ
ตอบคำถามต่อไปนี้
คำถามที่ 1
แท็ก <import> ในไฟล์เลย์เอาต์ XML มีหน้าที่อะไร
▢ รวมไฟล์เลย์เอาต์หนึ่งไว้ในอีกไฟล์หนึ่ง
▢ ฝังโค้ด Kotlin ไว้ในไฟล์เลย์เอาต์
▢ ให้สิทธิ์เข้าถึงพร็อพเพอร์ตี้ที่เชื่อมโยงกับข้อมูล
▢ ช่วยให้คุณอ้างอิงชั้นเรียนและสมาชิกในชั้นเรียนในนิพจน์การเชื่อมโยงได้
คำถามที่ 2
คุณจะเพิ่มตัวเลือกการค้นหาในการเรียกใช้เว็บเซอร์วิส REST ใน Retrofit ได้อย่างไร
▢ เพิ่มการค้นหาต่อท้าย URL ของคำขอ
▢ เพิ่มพารามิเตอร์สำหรับการค้นหาไปยังฟังก์ชันที่ส่งคำขอ และใส่คำอธิบายประกอบพารามิเตอร์นั้นด้วย @Query
▢ ใช้คลาส Query เพื่อสร้างคำขอ
▢ ใช้เมธอด addQuery() ในเครื่องมือสร้าง Retrofit
เริ่มบทเรียนถัดไป:
ดูลิงก์ไปยัง Codelab อื่นๆ ในหลักสูตรนี้ได้ที่หน้า Landing Page ของ Codelab หลักพื้นฐานของ Android Kotlin