Android Kotlin Fundamentals 08.3 Lọc và xem chi tiết bằng dữ liệu internet

Lớp học lập trình này nằm trong khóa học về Khái niệm cơ bản về Android Kotlin. Bạn sẽ nhận được nhiều giá trị nhất từ khóa học này nếu bạn làm việc qua các lớp học lập trình theo trình tự. Tất cả các lớp học lập trình trong khóa học đều có trên trang đích của các lớp học lập trình cơ bản về Android Kotlin.

Giới thiệu

Trong các lớp học lập trình trước đây của bài học này, bạn đã học cách lấy dữ liệu về bất động sản trên Mars từ một dịch vụ web, và cách tạo một RecyclerView bằng bố cục lưới để tải và hiển thị hình ảnh từ dữ liệu đó. Trong lớp học lập trình này, bạn đã hoàn thành ứng dụng MarsRealEstate bằng cách triển khai chức năng lọc các cơ sở lưu trú của sao Hỏa theo chế độ này để thuê hoặc mua. Bạn cũng tạo chế độ xem chi tiết để nếu người dùng nhấn vào ảnh tài sản trong tổng quan, họ sẽ thấy chế độ xem chi tiết về tài sản đó.

Những điều bạn nên biết

  • Cách tạo và sử dụng các mảnh (fragment).
  • Cách chuyển giữa các mảnh và sử dụng Safe Args (trình bổ trợ Gradle) để chuyển dữ liệu giữa các mảnh.
  • Cách sử dụng các thành phần cấu trúc bao gồm mô hình chế độ xem, nhà máy mô hình, phép biến đổi và LiveData.
  • Cách truy xuất dữ liệu đã mã hóa JSON từ dịch vụ web REST và phân tích cú pháp dữ liệu đó vào các đối tượng Kotlin bằng thư viện RetrofitMoshi.

Kiến thức bạn sẽ học được

  • Cách sử dụng biểu thức liên kết phức tạp trong tệp bố cục.
  • Cách tạo yêu cầu Retrofit cho một dịch vụ web bằng các tùy chọn truy vấn.

Bạn sẽ thực hiện

  • Sửa đổi ứng dụng MarsRealEstate để đánh dấu các cơ sở lưu trú là bán trên Mars (so với những tài sản cho thuê) có biểu tượng ký hiệu đô la.
  • Sử dụng trình đơn tùy chọn trên trang tổng quan để tạo một yêu cầu dịch vụ web sẽ lọc các thuộc tính của Mars theo loại.
  • Tạo một mảnh chi tiết cho một tài sản trên Mars, kết nối mảnh đó với lưới tổng quan bằng cách di chuyển và chuyển dữ liệu tài sản vào mảnh đó.

Trong lớp học lập trình này (và các lớp học lập trình liên quan), bạn làm việc với một ứng dụng có tên MarsRealEstate, nơi hiển thị các tài sản cần bán trên Mars. Ứng dụng này kết nối với một máy chủ Internet để truy xuất và hiển thị dữ liệu của cơ sở lưu trú, trong đó có các thông tin chi tiết như giá và cơ sở lưu trú để bán hoặc cho thuê. Hình ảnh đại diện cho mỗi cơ sở là ảnh thực tế từ sao Hỏa được chụp từ xe tự hành của Sao Hỏa Trong các lớp học lập trình trước đây, bạn đã tạo một RecyclerView bằng bố cục lưới cho tất cả ảnh của cơ sở lưu trú:

Trong phiên bản ứng dụng này, bạn làm việc với loại tài sản (Thuê và Mua) và thêm một biểu tượng vào bố cục lưới để đánh dấu các tài sản đang rao bán:

Bạn sửa đổi trình đơn tùy chọn của ứng dụng để lọc lưới để chỉ hiển thị những thuộc tính cho thuê hoặc để bán:

Cuối cùng, bạn tạo một chế độ xem chi tiết cho một tài sản riêng lẻ và kết nối các biểu tượng trên lưới tổng quan với mảnh chi tiết đó thông qua tính năng điều hướng:

Cho đến nay, phần duy nhất của dữ liệu tài sản trên Mars mà bạn đã sử dụng là URL của hình ảnh cơ sở lưu trú. Tuy nhiên, dữ liệu cơ sở lưu trú (mà bạn đã xác định trong lớp MarsProperty) cũng bao gồm mã nhận dạng, giá và loại (cho thuê hoặc bán). Để làm mới bộ nhớ của bạn, đây là đoạn mã của dữ liệu JSON mà bạn nhận được từ dịch vụ web:

{
   "price":8000000,
   "id":"424908",
   "type":"rent",
   "img_src": "http://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631290305226E03_DXXX.jpg"
},

Trong tác vụ này, bạn bắt đầu làm việc với loại thuộc tính Mars để thêm hình ảnh ký hiệu đô la vào các thuộc tính trên trang tổng quan đang rao bán.

Bước 1: Cập nhật MarsProperty để thêm loại

Lớp MarsProperty xác định cấu trúc dữ liệu cho từng thuộc tính do dịch vụ web cung cấp. Trong một lớp học lập trình trước đây, bạn đã sử dụng thư viện Moshi để phân tích cú pháp phản hồi JSON thô từ dịch vụ web Mars thành từng đối tượng dữ liệu MarsProperty.

Trong bước này, bạn thêm một số logic vào lớp MarsProperty để cho biết liệu một tài sản có cho thuê hay không (nghĩa là loại đó là chuỗi "rent" hay "buy"). Bạn sẽ sử dụng logic này ở nhiều vị trí, do đó, tốt hơn là bạn nên đặt logic ở đây trong lớp dữ liệu so với sao chép.

  1. Mở ứng dụng MarsRealEstate từ lớp học lập trình cuối cùng. (Bạn có thể tải MarsRealEstateGrid xuống nếu bạn không có ứng dụng.)
  2. Mở network/MarsProperty.kt. Thêm phần nội dung vào định nghĩa lớp MarsProperty và thêm phương thức getter tùy chỉnh cho isRental trả về true nếu đối tượng này thuộc loại "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"
}

Bước 2: Cập nhật bố cục mục lưới

Bây giờ, bạn hãy cập nhật bố cục mục cho lưới hình ảnh để chỉ hiển thị một ký hiệu đô la có thể vẽ bằng đô la trên những hình ảnh tài sản đang rao bán:

Với biểu thức liên kết dữ liệu, bạn có thể kiểm tra toàn bộ trong bố cục XML cho các mục lưới.

  1. Mở res/layout/grid_view_item.xml. Đây là tệp bố cục cho từng ô trong bố cục lưới cho RecyclerView. Hiện tại, tệp chỉ chứa phần tử <ImageView> cho hình ảnh tài sản.
  2. Bên trong phần tử <data>, hãy thêm một phần tử <import> cho lớp View. Bạn có thể dùng tính năng nhập khi muốn sử dụng các thành phần của một lớp bên trong một biểu thức liên kết dữ liệu trong một tệp bố cục. Trong trường hợp này, bạn sẽ dùng hằng số View.GONEView.VISIBLE, vì vậy, bạn cần có quyền truy cập vào lớp View.
<import type="android.view.View"/>
  1. Bao quanh toàn bộ chế độ xem hình ảnh bằng dấu FrameLayout, để cho phép vẽ ký hiệu đô la xếp chồng lên trên hình ảnh tài sản.
<FrameLayout
   android:layout_width="match_parent"
   android:layout_height="170dp">
             <ImageView 
                    android:id="@+id/mars_image"
            ...
</FrameLayout>
  1. Đối với ImageView, hãy thay đổi thuộc tính android:layout_height thành match_parent để đáp ứng nhu cầu mới của nhà xuất bản mẹ FrameLayout.
android:layout_height="match_parent"
  1. Thêm phần tử <ImageView> thứ hai ngay bên dưới phần tử đầu tiên, bên trong FrameLayout. Sử dụng định nghĩa được hiển thị bên dưới. Hình ảnh này xuất hiện ở góc dưới bên phải của mục lưới, ở đầu hình ảnh Sao Hỏa và sử dụng bản vẽ có thể vẽ được xác định trong res/drawable/ic_for_sale_outline.xml cho biểu tượng ký hiệu đô la.
<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. Thêm thuộc tính android:visibility vào chế độ xem hình ảnh mars_property_type. Sử dụng biểu thức ràng buộc để kiểm tra loại thuộc tính và chỉ định chế độ hiển thị cho View.GONE (đối với nội dung cho thuê) hoặc View.VISIBLE (đối với giao dịch mua).
 android:visibility="@{property.rental ? View.GONE : View.VISIBLE}"

Từ trước đến nay, bạn chỉ thấy các biểu thức ràng buộc trong bố cục sử dụng các biến riêng lẻ được xác định trong phần tử <data>. Biểu thức liên kết cực kỳ mạnh mẽ và cho phép bạn thực hiện các thao tác như kiểm tra và tính toán hoàn toàn trong bố cục XML của bạn. Trong trường hợp này, bạn sử dụng toán tử thứ ba (?:) để thực hiện kiểm tra (đối tượng này có phải là nội dung cho thuê không?). Bạn cung cấp một kết quả cho giá trị đúng (ẩn biểu tượng ký hiệu đô la với View.GONE) và một kết quả khác cho giá trị sai (hiển thị biểu tượng đó với View.VISIBLE).

Tệp grid_view_item.xml hoàn chỉnh mới được hiển thị bên dưới:

<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. Biên dịch và chạy ứng dụng cũng như lưu ý rằng những cơ sở lưu trú không phải là nhà nghỉ cho thuê sẽ có biểu tượng ký hiệu đô la.

Hiện tại, ứng dụng của bạn hiển thị tất cả các thuộc tính của Mars trong lưới tổng quan. Nếu người dùng mua bất động sản cho thuê trên Mars, việc có các biểu tượng để cho biết bất động sản nào có sẵn để bán sẽ hữu ích, nhưng vẫn có rất nhiều thuộc tính cuộn qua trên trang. Trong nhiệm vụ này, bạn thêm một trình đơn tùy chọn vào mảnh tổng quan cho phép người dùng chỉ hiển thị nhà nghỉ cho thuê, chỉ cho thuê để bán hoặc hiển thị tất cả.

Một cách để thực hiện tác vụ này là kiểm tra loại cho mỗi MarsProperty trong lưới tổng quan và chỉ hiển thị các thuộc tính phù hợp. Tuy nhiên, dịch vụ web Mars thực tế có thông số hoặc tùy chọn truy vấn (được gọi là filter) cho phép bạn chỉ nhận các thuộc tính thuộc loại rent hoặc loại buy. Bạn có thể sử dụng truy vấn bộ lọc này với URL dịch vụ web realestate trong một trình duyệt như sau:

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

Trong tác vụ này, bạn sửa đổi lớp MarsApiService để thêm tùy chọn truy vấn vào yêu cầu dịch vụ web bằng Retrofit. Sau đó, bạn kết nối với trình đơn tùy chọn để tải lại tất cả dữ liệu thuộc tính trên Mars xuống bằng cách sử dụng tùy chọn truy vấn đó. Vì phản hồi bạn nhận được từ dịch vụ web chỉ chứa các thuộc tính mà bạn quan tâm, bạn không cần phải thay đổi logic hiển thị chế độ xem cho lưới tổng quan.

Bước 1: Cập nhật dịch vụ API Mars

Để thay đổi yêu cầu, bạn cần truy cập lại vào lớp MarsApiService mà bạn đã triển khai trong lớp học lập trình đầu tiên trong chuỗi này. Bạn sửa đổi lớp để cung cấp API lọc.

  1. Mở network/MarsApiService.kt. Ngay bên dưới quá trình nhập, hãy tạo enum có tên là MarsApiFilter để xác định hằng số khớp với giá trị truy vấn mà dịch vụ web muốn.
enum class MarsApiFilter(val value: String) {
   SHOW_RENT("rent"),
   SHOW_BUY("buy"),
   SHOW_ALL("all") }
  1. Sửa đổi phương thức getProperties() để nhận giá trị nhập vào dạng chuỗi cho cụm từ tìm kiếm bộ lọc và chú thích dữ liệu đầu vào đó bằng @Query("filter"), như minh họa bên dưới.

    Nhập retrofit2.http.Query khi được nhắc.

    Chú thích @Query cho biết phương thức getProperties() (và do đó, Retrofit) để thực hiện yêu cầu dịch vụ web bằng tùy chọn bộ lọc. Mỗi lần getProperties() được gọi, URL yêu cầu sẽ bao gồm phần ?filter=type. Phần này sẽ chuyển hướng dịch vụ web để phản hồi với kết quả khớp với truy vấn đó.
fun getProperties(@Query("filter") type: String):  

Bước 2: Cập nhật mô hình chế độ xem tổng quan

Bạn yêu cầu dữ liệu từ MarsApiService trong phương thức getMarsRealEstateProperties() trong OverviewViewModel. Bây giờ, bạn cần cập nhật yêu cầu đó để lấy đối số bộ lọc.

  1. Mở overview/OverviewViewModel.kt. Bạn sẽ thấy lỗi trong Android Studio do những thay đổi bạn đã thực hiện trong bước trước đó. Thêm MarsApiFilter (enum của các giá trị bộ lọc có thể sử dụng) làm thông số cho lệnh gọi getMarsRealEstateProperties().

    Nhập com.example.android.marsrealestate.network.MarsApiFilter khi được yêu cầu.
private fun getMarsRealEstateProperties(filter: MarsApiFilter) {
  1. Sửa đổi lệnh gọi thành getProperties() trong dịch vụ Retrofit để chuyển truy vấn bộ lọc đó dưới dạng một chuỗi.
var getPropertiesDeferred = MarsApi.retrofitService.getProperties(filter.value)
  1. Trong khối init {}, hãy chuyển MarsApiFilter.SHOW_ALL làm đối số cho getMarsRealEstateProperties(), để hiển thị tất cả các tài sản khi ứng dụng tải lần đầu tiên.
init {
   getMarsRealEstateProperties(MarsApiFilter.SHOW_ALL)
}
  1. Vào cuối lớp, hãy thêm một phương thức updateFilter() lấy đối số MarsApiFilter và gọi getMarsRealEstateProperties() bằng đối số đó.
fun updateFilter(filter: MarsApiFilter) {
   getMarsRealEstateProperties(filter)
}

Bước 3: Kết nối mảnh với trình đơn tùy chọn

Bước cuối cùng là kết nối trình đơn mục bổ sung với mảnh để gọi updateFilter() trên mô hình chế độ xem khi người dùng chọn một tùy chọn trong trình đơn.

  1. Mở res/menu/overflow_menu.xml. Ứng dụng MarsRealEstate có một trình đơn mục bổ sung hiện có, cung cấp ba tùy chọn có sẵn: hiển thị tất cả các cơ sở lưu trú, chỉ hiển thị nội dung cho thuê và chỉ hiển thị các cơ sở lưu trú để bán.
<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. Mở overview/OverviewFragment.kt. Khi kết thúc lớp học, hãy triển khai phương thức onOptionsItemSelected() để xử lý các mục trong trình đơn.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
} 
  1. Trong onOptionsItemSelected(), hãy gọi phương thức updateFilter() trên mô hình chế độ xem bằng bộ lọc thích hợp. Dùng một khối Kotlin when {} để chuyển đổi giữa các lựa chọn. Sử dụng MarsApiFilter.SHOW_ALL cho giá trị bộ lọc mặc định. Quay lại true vì bạn đã xử lý mục trong trình đơn. Nhập MarsApiFilter (com.example.android.marsrealestate.network.MarsApiFilter) khi được yêu cầu. Dưới đây là phương thức onOptionsItemSelected() hoàn chỉnh.
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. Biên dịch và chạy ứng dụng. Ứng dụng chạy lưới tổng quan đầu tiên với tất cả loại tài sản và những tài sản bán cho được đánh dấu bằng biểu tượng đô la.
  2. Chọn Thuê từ trình đơn tùy chọn. Các tài sản sẽ được tải lại và không có tài sản nào xuất hiện cùng biểu tượng đô la. (Chỉ hiển thị các cơ sở lưu trú cho thuê.) Bạn có thể phải đợi vài phút để màn hình làm mới để chỉ hiển thị những thuộc tính đã lọc.
  3. Chọn Mua từ trình đơn tùy chọn. Các thuộc tính sẽ tải lại và tất cả các thuộc tính đó xuất hiện với biểu tượng đô la. (Chỉ hiển thị các cơ sở lưu trú để bán).

Giờ đây, bạn có một lưới các biểu tượng cuộn cho các tài sản trên Mars, nhưng đã đến lúc chi tiết hơn. Trong nhiệm vụ này, bạn thêm một mảnh chi tiết để hiển thị thông tin chi tiết về một tài sản cụ thể. Mảnh chi tiết sẽ hiển thị một hình ảnh lớn hơn, giá và loại tài sản (cho dù đó là tiền cho thuê hay để bán).

Mảnh này sẽ chạy khi người dùng nhấn vào một hình ảnh trong lưới tổng quan. Để thực hiện việc này, bạn cần thêm một trình nghe onClick vào các mục lưới RecyclerView, sau đó chuyển đến mảnh mới. Bạn di chuyển bằng cách kích hoạt thay đổi LiveData trong ViewModel, như bạn đã làm trong suốt các bài học này. Bạn cũng sử dụng trình bổ trợ Safe Args của thành phần điều hướng để chuyển thông tin MarsProperty đã chọn từ mảnh tổng quan đến mảnh chi tiết.

Bước 1: Tạo mô hình chế độ xem chi tiết và cập nhật bố cục chi tiết

Tương tự như quy trình bạn đã sử dụng cho mô hình chế độ xem tổng quan và các mảnh, giờ đây, bạn cần phải triển khai mô hình chế độ xem và các tệp bố cục cho mảnh chi tiết.

  1. Mở detail/DetailViewModel.kt. Cũng giống như các tệp Kotlin liên quan đến mạng nằm trong thư mục network và các tệp tổng quan trong overview, thư mục detail chứa các tệp được liên kết với chế độ xem chi tiết. Xin lưu ý rằng lớp DetailViewModel (hiện đang trống) lấy marsProperty làm thông số trong hàm dựng.
class DetailViewModel( marsProperty: MarsProperty,
                     app: Application) : AndroidViewModel(app) {
}
  1. Bên trong phần xác định lớp, hãy thêm LiveData cho thuộc tính Mars đã chọn, để hiển thị thông tin đó ở chế độ xem chi tiết. Hãy làm theo mẫu thông thường để tạo MutableLiveData để giữ riêng MarsProperty, sau đó để lại thuộc tính công khai LiveData không thể thay đổi.

    Nhập androidx.lifecycle.LiveData và nhập androidx.lifecycle.MutableLiveData khi được yêu cầu.
private val _selectedProperty = MutableLiveData<MarsProperty>()
val selectedProperty: LiveData<MarsProperty>
   get() = _selectedProperty
  1. Tạo một khối init {} và đặt giá trị của thuộc tính Mars đã chọn bằng đối tượng MarsProperty trong hàm dựng.
    init {
        _selectedProperty.value = marsProperty
    }
  1. Mở res/layout/fragment_detail.xml và xem đối tượng đó trong chế độ xem thiết kế.

    Đây là tệp bố cục cho mảnh chi tiết. Ảnh này chứa ImageView cho ảnh lớn, TextView cho loại tài sản (cho thuê hoặc bán) và TextView cho giá. Xin lưu ý rằng bố cục hạn chế được gói bằng ScrollView để tự động cuộn nếu chế độ xem quá lớn so với màn hình, ví dụ như khi người dùng xem chế độ xem đó ở chế độ ngang.
  2. Chuyển đến thẻ Văn bản của bố cục. Ở đầu bố cục, ngay trước phần tử <ScrollView>, hãy thêm một phần tử <data> để liên kết mô hình chế độ xem chi tiết với bố cục.
<data>
   <variable
       name="viewModel"
       type="com.example.android.marsrealestate.detail.DetailViewModel" />
</data>
  1. Hãy thêm thuộc tính app:imageUrl vào phần tử ImageView. Hãy đặt giá trị này thành imgSrcUrl từ thuộc tính đã chọn của mô hình chế độ xem.

    Bộ chuyển đổi liên kết tải hình ảnh bằng cách dùng Glide sẽ tự động được dùng ở đây, vì bộ chuyển đổi đó xem tất cả các thuộc tính app:imageUrl.
 app:imageUrl="@{viewModel.selectedProperty.imgSrcUrl}"

Bước 2: Xác định cách di chuyển trong mô hình chế độ xem tổng quan

Khi người dùng nhấn vào một ảnh trong mô hình tổng quan, thao tác này sẽ kích hoạt thao tác di chuyển đến một mảnh hiển thị thông tin chi tiết về mục đã nhấp.

  1. Mở overview/OverviewViewModel.kt. Thêm thuộc tính _navigateToSelectedProperty MutableLiveData và hiển thị thuộc tính đó bằng LiveData không thể thay đổi.

    Khi LiveData này chuyển thành không có giá trị null, trình điều hướng sẽ được kích hoạt. (Sắp tới, bạn sẽ thêm mã để quan sát biến này và kích hoạt bảng điều hướng.)
private val _navigateToSelectedProperty = MutableLiveData<MarsProperty>()
val navigateToSelectedProperty: LiveData<MarsProperty>
   get() = _navigateToSelectedProperty
  1. Vào cuối lớp, hãy thêm một phương thức displayPropertyDetails() đặt _navigateToSelectedProperty vào thuộc tính Mars đã chọn.
fun displayPropertyDetails(marsProperty: MarsProperty) {
   _navigateToSelectedProperty.value = marsProperty
}
  1. Thêm phương thức displayPropertyDetailsComplete() sẽ rỗng giá trị của _navigateToSelectedProperty. Bạn cần đánh dấu trạng thái điều hướng này để hoàn tất và để tránh điều hướng được kích hoạt lại khi người dùng quay lại từ chế độ xem chi tiết.
fun displayPropertyDetailsComplete() {
   _navigateToSelectedProperty.value = null
}

Bước 3: Thiết lập trình xử lý lượt nhấp trong bộ chuyển đổi lưới và mảnh

  1. Mở overview/PhotoGridAdapter.kt. Vào cuối lớp, hãy tạo một lớp OnClickListener tùy chỉnh lấy hàm lambda có thông số marsProperty. Bên trong lớp, hãy xác định một hàm onClick() được đặt thành thông số lambda.
class OnClickListener(val clickListener: (marsProperty:MarsProperty) -> Unit) {
     fun onClick(marsProperty:MarsProperty) = clickListener(marsProperty)
}
  1. Chuyển đến phần xác định lớp cho PhotoGridAdapter và thêm một thuộc tính OnClickListener riêng tư vào hàm dựng.
class PhotoGridAdapter( private val onClickListener: OnClickListener ) :
       ListAdapter<MarsProperty,              
           PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
  1. Tạo một ảnh có thể nhấp được bằng cách thêm onClickListener vào mục lưới trong phương thức onBindviewHolder(). Xác định trình xử lý lượt nhấp giữa các lệnh gọi tới getItem() and bind().
override fun onBindViewHolder(holder: MarsPropertyViewHolder, position: Int) {
   val marsProperty = getItem(position)
   holder.itemView.setOnClickListener {
       onClickListener.onClick(marsProperty)
   }
   holder.bind(marsProperty)
}
  1. Mở overview/OverviewFragment.kt. Trong phương thức onCreateView(), hãy thay thế dòng khởi chạy thuộc tính binding.photosGrid.adapter bằng dòng hiển thị bên dưới.

    Mã này thêm đối tượng PhotoGridAdapter.onClickListener vào hàm dựng PhotoGridAdapter và gọi viewModel.displayPropertyDetails() bằng đối tượng MarsProperty đã chuyển. Thao tác này sẽ kích hoạt LiveData trong mô hình chế độ xem để thao tác.
binding.photosGrid.adapter = PhotoGridAdapter(PhotoGridAdapter.OnClickListener {
   viewModel.displayPropertyDetails(it)
})

Bước 4: Sửa đổi sơ đồ điều hướng và đặt tài sản MarsProperty theo gói

Khi người dùng nhấn vào một ảnh trong lưới tổng quan, ứng dụng sẽ chuyển đến mảnh chi tiết và chuyển qua các chi tiết của thuộc tính Mars đã chọn để chế độ xem chi tiết có thể hiển thị thông tin đó.

Hiện tại, bạn có trình nghe lượt nhấp từ PhotoGridAdapter để xử lý thao tác nhấn và cách kích hoạt trình điều hướng từ mô hình chế độ xem. Tuy nhiên, bạn chưa chuyển đối tượng MarsProperty vào mảnh chi tiết. Để làm được việc đó, bạn hãy dùng Safe Args từ thành phần điều hướng.

  1. Mở res/navigation/nav_graph.xml. Nhấp vào thẻ Văn bản để xem mã XML cho sơ đồ điều hướng.
  2. Bên trong phần tử <fragment> cho mảnh chi tiết, hãy thêm phần tử <argument> được hiển thị bên dưới. Đối số này, có tên là selectedProperty, có loại MarsProperty.
<argument
   android:name="selectedProperty"
   app:argType="com.example.android.marsrealestate.network.MarsProperty"
   />
  1. Biên dịch ứng dụng. Quá trình điều hướng gặp lỗi vì MarsProperty không có thể giao dịch. Giao diện Parcelable cho phép các đối tượng được chuyển đổi tuần tự, để các đối tượng\39; dữ liệu có thể được chuyển qua lại giữa các mảnh hoặc hoạt động. Trong trường hợp này, để chuyển dữ liệu bên trong đối tượng MarsProperty đến mảnh chi tiết thông qua Safe Args, MarsProperty phải triển khai giao diện Parcelable. Tin vui là Kotlin cung cấp một lối tắt dễ dàng để triển khai giao diện đó.
  2. Mở network/MarsProperty.kt. Thêm chú thích @Parcelize vào định nghĩa lớp.

    Nhập kotlinx.android.parcel.Parcelize khi được yêu cầu.

    Chú thích @Parcelize sử dụng các tiện ích của Kotlin dành cho Android để tự động triển khai các phương thức trong giao diện Parcelable cho lớp này. Bạn không phải làm bất cứ việc gì khác!
@Parcelize
data class MarsProperty (
  1. Thay đổi định nghĩa lớp của MarsProperty để mở rộng Parcelable.

    Nhập android.os.Parcelable khi được yêu cầu.

    Giờ đây, định nghĩa lớp MarsProperty có dạng như sau:
@Parcelize
data class MarsProperty (
       val id: String,
       @Json(name = "img_src") val imgSrcUrl: String,
       val type: String,
       val price: Double) : Parcelable {

Bước 5: Kết nối các mảnh

Bạn vẫn không di chuyển – điều hướng thực tế diễn ra trong các mảnh. Trong bước này, bạn thêm các bit cuối cùng để triển khai bảng điều hướng giữa các phần tổng quan và các mảnh chi tiết.

  1. Mở overview/OverviewFragment.kt. Trong onCreateView(), bên dưới các dòng khởi chạy bộ chuyển đổi lưới ảnh, hãy thêm các dòng hiển thị bên dưới để quan sát navigatedToSelectedProperty từ mô hình chế độ xem tổng quan.

    Nhập androidx.lifecycle.Observer và nhập androidx.navigation.fragment.findNavController khi được yêu cầu.

    Trình quan sát kiểm tra xem MarsPropertyit trong hàm lambda có hay không, và nếu có thì nó sẽ nhận bộ điều khiển điều hướng từ mảnh với findNavController(). Gọi displayPropertyDetailsComplete() để yêu cầu mô hình chế độ xem đặt lại LiveData về trạng thái rỗng, vì vậy, bạn sẽ không vô tình kích hoạt lại thao tác điều hướng khi ứng dụng quay trở lại OverviewFragment.
viewModel.navigateToSelectedProperty.observe(this, Observer {
   if ( null != it ) {   
      this.findNavController().navigate(
              OverviewFragmentDirections.actionShowDetail(it))             
      viewModel.displayPropertyDetailsComplete()
   }
})
  1. Mở detail/DetailFragment.kt. Thêm dòng này ngay bên dưới cuộc gọi vào setLifecycleOwner() trong phương thức onCreateView(). Dòng này nhận đối tượng MarsProperty được chọn từ Safe Args.

    Lưu ý rằng việc sử dụng toán tử xác nhận không có giá trị null (!!) nếu selectedProperty không có, thì có điều gì đó rất tệ đã xảy ra và bạn thực sự muốn mã gửi một con trỏ rỗng. (Trong mã sản xuất, bạn nên xử lý lỗi đó theo cách nào đó.)
 val marsProperty = DetailFragmentArgs.fromBundle(arguments!!).selectedProperty
  1. Thêm dòng này tiếp theo để nhận DetailViewModelFactory mới. Bạn sẽ sử dụng DetailViewModelFactory để nhận bản sao của DetailViewModel. Ứng dụng dành cho người mới bắt đầu bao gồm hoạt động triển khai DetailViewModelFactory, do đó, tất cả những gì bạn cần làm ở đây là khởi chạy ứng dụng.
val viewModelFactory = DetailViewModelFactory(marsProperty, application)
  1. Cuối cùng, hãy thêm dòng này để lấy DetailViewModel từ nhà máy và kết nối tất cả các bộ phận.
      binding.viewModel = ViewModelProviders.of(
                this, viewModelFactory).get(DetailViewModel::class.java)
  1. Biên dịch và chạy ứng dụng rồi nhấn vào một ảnh bất động sản trên Mars. Mảnh chi tiết sẽ xuất hiện cho các thông tin chi tiết của tài sản đó. Nhấn vào nút Quay lại để quay lại trang tổng quan và nhận thấy rằng màn hình chi tiết vẫn ở dạng thưa thớt. Bạn sẽ hoàn tất việc thêm dữ liệu tài sản vào trang chi tiết đó trong nhiệm vụ tiếp theo.

Hiện tại, trang chi tiết chỉ hiển thị cùng một ảnh trên Mars mà bạn đã thấy trên trang tổng quan. Lớp MarsProperty cũng có một loại tài sản (thuê hoặc mua) và giá tài sản. Màn hình thông tin chi tiết phải bao gồm cả hai giá trị này và sẽ hữu ích nếu thuộc tính cho thuê cho biết giá là giá trị mỗi tháng. Bạn sử dụng phép biến đổi LiveData trong mô hình chế độ xem để triển khai cả hai phép biến đổi đó.

  1. Mở res/values/strings.xml. Mã dành cho người mới bắt đầu bao gồm các tài nguyên chuỗi (hiển thị bên dưới) để giúp bạn tạo chuỗi cho chế độ xem chi tiết. Đối với giá, bạn sẽ sử dụng tài nguyên display_price_monthly_rental hoặc tài nguyên display_price, tùy thuộc vào loại tài sản.
<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. Mở detail/DetailViewModel.kt. Ở cuối lớp học, hãy thêm mã hiển thị bên dưới.

    Nhập androidx.lifecycle.Transformations nếu được yêu cầu.

    Quá trình chuyển đổi này kiểm tra xem tài sản đã chọn có phải là tài sản cho thuê hay không bằng cách sử dụng cùng một thử nghiệm từ nhiệm vụ đầu tiên. Nếu tài sản là tài sản cho thuê, thì quá trình chuyển đổi sẽ chọn chuỗi thích hợp từ các tài nguyên bằng một công tắc Kotlin when {}. Cả hai chuỗi này cần một số ở cuối, vì vậy, bạn sẽ nối property.price sau đó.
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. Nhập lớp R đã tạo để truy cập vào các tài nguyên chuỗi trong dự án.
import com.example.android.marsrealestate.R
  1. Sau khi chuyển đổi displayPropertyPrice, hãy thêm mã ở bên dưới. Sự chuyển đổi này liên kết nhiều tài nguyên chuỗi, dựa trên việc loại tài sản có phải là nhà nghỉ cho thuê hay không.
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. Mở res/layout/fragment_detail.xml. Bạn chỉ cần làm thêm một việc nữa, đó là liên kết các chuỗi mới (mà bạn đã tạo với các phép biến đổi LiveData) vào chế độ xem chi tiết. Để làm việc đó, bạn đặt giá trị của trường văn bản cho văn bản loại thuộc tính thành viewModel.displayPropertyType và trường văn bản cho văn bản giá trị giá thành 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. Biên dịch và chạy ứng dụng. Bây giờ, tất cả dữ liệu thuộc tính sẽ xuất hiện trên trang chi tiết, được định dạng chính xác.

Dự án Android Studio: MarsRealEstatefinal

Biểu thức liên kết

  • Sử dụng biểu thức liên kết trong tệp bố cục XML để thực hiện các phép toán có lập trình đơn giản, chẳng hạn như thử nghiệm toán học hoặc có điều kiện, trên dữ liệu ràng buộc.
  • Để tham chiếu đến các lớp bên trong tệp bố cục, hãy sử dụng thẻ <import> bên trong thẻ <data>.

Tùy chọn truy vấn dịch vụ web

  • Yêu cầu đối với dịch vụ web có thể bao gồm các thông số không bắt buộc.
  • Để chỉ định các tham số truy vấn trong yêu cầu, hãy dùng chú thích @Query trong Retrofit.

Khóa học từ Udacity:

Tài liệu dành cho nhà phát triển Android:

Các tài liệu khác:

Phần này liệt kê các bài tập về nhà có thể được giao cho học viên đang làm việc qua lớp học lập trình này trong khóa học do người hướng dẫn tổ chức. Người hướng dẫn có thể làm những việc sau:

  • Giao bài tập về nhà nếu được yêu cầu.
  • Trao đổi với học viên cách nộp bài tập về nhà.
  • Chấm điểm bài tập về nhà.

Người hướng dẫn có thể sử dụng những đề xuất này ít hay nhiều tùy ý. Do đó, họ có thể thoải mái giao bất kỳ bài tập về nhà nào khác mà họ cảm thấy phù hợp.

Nếu bạn đang tự mình làm việc qua lớp học lập trình này, hãy thoải mái sử dụng các bài tập về nhà này để kiểm tra kiến thức của bạn.

Trả lời những câu hỏi này

Câu hỏi 1

Thẻ <import> trong tệp bố cục XML có chức năng gì?

▢ Bao gồm một tệp bố cục trong một tệp bố cục khác.

▢ Nhúng mã Kotlin vào trong tệp bố cục.

▢ Cung cấp quyền truy cập vào các tài sản liên kết dữ liệu.

▢ Cho phép bạn tham chiếu đến các lớp và thành viên trong lớp trong biểu thức liên kết.

Câu hỏi 2

Bạn có thể làm cách nào để thêm lựa chọn truy vấn vào lệnh gọi dịch vụ web Kiến trúc chuyển trạng thái đại diện (REST) trong Retrofit?

▢ Thêm truy vấn vào cuối URL yêu cầu.

▢ Thêm một tham số cho truy vấn vào hàm tạo yêu cầu và chú thích tham số đó bằng @Query.

▢ Sử dụng lớp Query để tạo yêu cầu.

▢ Sử dụng phương thức addQuery() trong trình tạo Retrofit.

Bắt đầu bài học tiếp theo: 9.1: Kho lưu trữ

Để biết đường liên kết đến các lớp học lập trình khác trong khóa học này, hãy xem trang đích của các lớp học lập trình cơ bản về Android Kotlin.