Kiến thức cơ bản về Kotlin cho Android 07.2: DiffUtil và liên kết dữ liệu bằng RecyclerView

Lớp học lập trình này thuộc khoá học Kiến thức cơ bản về Kotlin cho Android. Bạn sẽ nhận được nhiều giá trị nhất qua khoá học này nếu thực hiện các lớp học lập trình theo trình tự. Tất cả lớp học lập trình của khoá học đều được liệt kê trên trang đích của lớp học lập trình Kiến thức cơ bản về cách tạo ứng dụng Android bằng Kotlin.

Giới thiệu

Trong lớp học lập trình trước, bạn đã cập nhật ứng dụng TrackMySleepQuality để hiển thị dữ liệu về chất lượng giấc ngủ trong RecyclerView. Các kỹ thuật mà bạn đã học được khi tạo RecyclerView đầu tiên là đủ cho hầu hết các RecyclerViews hiển thị danh sách đơn giản không quá lớn. Tuy nhiên, có một số kỹ thuật giúp RecyclerView hiệu quả hơn cho các danh sách lớn, đồng thời giúp mã của bạn dễ duy trì hơn và mở rộng cho các danh sách và lưới phức tạp.

Trong lớp học lập trình này, bạn sẽ phát triển ứng dụng theo dõi giấc ngủ từ lớp học lập trình trước. Bạn sẽ tìm hiểu một cách hiệu quả hơn để cập nhật danh sách dữ liệu về giấc ngủ và tìm hiểu cách sử dụng tính năng liên kết dữ liệu với RecyclerView. (Nếu không có ứng dụng từ lớp học lập trình trước, bạn có thể tải mã khởi đầu cho lớp học lập trình này.)

Kiến thức bạn cần có

  • Xây dựng giao diện người dùng cơ bản bằng cách sử dụng một hoạt động, các mảnh và thành phần hiển thị.
  • Điều hướng giữa các mảnh và sử dụng safeArgs để truyền dữ liệu giữa các mảnh.
  • Xem các mô hình, nhà máy mô hình hiển thị, các phép biến đổi và LiveData cũng như các đối tượng theo dõi của chúng.
  • Cách tạo cơ sở dữ liệu Room, tạo DAO và xác định các thực thể.
  • Cách sử dụng coroutine cho cơ sở dữ liệu và các tác vụ chạy trong thời gian dài khác.
  • Cách triển khai một RecyclerView cơ bản bằng Adapter, ViewHolder và bố cục thành phần.

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

  • Cách sử dụng DiffUtil để cập nhật hiệu quả danh sách do RecyclerView hiển thị.
  • Cách sử dụng tính năng liên kết dữ liệu với RecyclerView.
  • Cách sử dụng trình chuyển đổi liên kết để chuyển đổi dữ liệu.

Bạn sẽ thực hiện

  • Xây dựng trên ứng dụng TrackMySleepQuality từ lớp học lập trình trước trong loạt lớp học lập trình này.
  • Cập nhật SleepNightAdapter để cập nhật danh sách một cách hiệu quả bằng cách sử dụng DiffUtil.
  • Triển khai liên kết dữ liệu cho RecyclerView, sử dụng bộ chuyển đổi liên kết để chuyển đổi dữ liệu.

Ứng dụng theo dõi giấc ngủ có 2 màn hình, được biểu thị bằng các mảnh, như trong hình dưới đây.

Màn hình đầu tiên (xuất hiện ở bên trái) có các nút để bắt đầu và dừng theo dõi. Màn hình này cho thấy một số dữ liệu về giấc ngủ của người dùng. Nút Xoá sẽ xoá vĩnh viễn tất cả dữ liệu mà ứng dụng đã thu thập cho người dùng. Màn hình thứ hai (ở bên phải) là màn hình chọn mức đánh giá chất lượng giấc ngủ.

Ứng dụng này được thiết kế để sử dụng một bộ điều khiển giao diện người dùng, ViewModelLiveData, cũng như một cơ sở dữ liệu Room để duy trì dữ liệu giấc ngủ.

Dữ liệu giấc ngủ được hiển thị trong RecyclerView. Trong lớp học lập trình này, bạn sẽ tạo DiffUtil và phần liên kết dữ liệu cho RecyclerView. Sau lớp học lập trình này, ứng dụng của bạn sẽ trông hoàn toàn giống nhau, nhưng sẽ hiệu quả hơn và dễ dàng mở rộng cũng như duy trì hơn.

Bạn có thể tiếp tục sử dụng ứng dụng SleepTracker trong lớp học lập trình trước hoặc tải ứng dụng RecyclerViewDiffUtilDataBinding-Starter xuống qua GitHub.

  1. Nếu cần, hãy tải ứng dụng RecyclerViewDiffUtilDataBinding-Starter xuống từ GitHub rồi mở dự án trong Android Studio.
  2. Chạy ứng dụng.
  3. Mở tệp SleepNightAdapter.kt.
  4. Kiểm tra mã để làm quen với cấu trúc của ứng dụng. Hãy tham khảo sơ đồ bên dưới để xem lại cách sử dụng RecyclerView với mẫu bộ chuyển đổi để hiển thị dữ liệu về giấc ngủ cho người dùng.

  • Từ thông tin đầu vào của người dùng, ứng dụng sẽ tạo một danh sách các đối tượng SleepNight. Mỗi đối tượng SleepNight đại diện cho một đêm ngủ, thời lượng và chất lượng của giấc ngủ đó.
  • SleepNightAdapter điều chỉnh danh sách các đối tượng SleepNight thành một thứ mà RecyclerView có thể sử dụng và hiển thị.
  • Trình kết nối SleepNightAdapter tạo ra ViewHolders chứa các khung hiển thị, dữ liệu và thông tin meta để khung hiển thị trình tái chế hiển thị dữ liệu.
  • RecyclerView sử dụng SleepNightAdapter để xác định số lượng mục cần hiển thị (getItemCount()). RecyclerView sử dụng onCreateViewHolder()onBindViewHolder() để lấy các trình giữ khung hiển thị được liên kết với dữ liệu để hiển thị.

Phương thức notifyDataSetChanged() không hiệu quả

Để cho RecyclerView biết rằng một mục trong danh sách đã thay đổi và cần được cập nhật, mã hiện tại sẽ gọi notifyDataSetChanged() trong SleepNightAdapter, như minh hoạ bên dưới.

var data =  listOf<SleepNight>()
   set(value) {
       field = value
       notifyDataSetChanged()
   }

Tuy nhiên, notifyDataSetChanged() cho RecyclerView biết rằng toàn bộ danh sách có khả năng không hợp lệ. Do đó, RecyclerView sẽ liên kết lại và vẽ lại mọi mục trong danh sách, kể cả những mục không hiển thị trên màn hình. Đây là rất nhiều công việc không cần thiết. Đối với các danh sách lớn hoặc phức tạp, quá trình này có thể mất nhiều thời gian đến mức màn hình nhấp nháy hoặc giật khi người dùng cuộn qua danh sách.

Để khắc phục vấn đề này, bạn có thể cho RecyclerView biết chính xác những gì đã thay đổi. Sau đó, RecyclerView chỉ có thể cập nhật những khung hiển thị đã thay đổi trên màn hình.

RecyclerView có một API đa dạng để cập nhật một phần tử duy nhất. Bạn có thể dùng notifyItemChanged() để cho RecyclerView biết rằng một mục đã thay đổi và bạn có thể dùng các hàm tương tự cho những mục được thêm, xoá hoặc di chuyển. Bạn có thể làm tất cả theo cách thủ công, nhưng đó là một việc không hề đơn giản và có thể liên quan đến khá nhiều mã.

May mắn thay, có một cách hay hơn.

DiffUtil hoạt động hiệu quả và giúp bạn thực hiện những công việc khó khăn

RecyclerView có một lớp gọi là DiffUtil dùng để tính toán sự khác biệt giữa hai danh sách. DiffUtil lấy một danh sách cũ và một danh sách mới rồi tìm ra điểm khác biệt. Công cụ này tìm thấy những mục đã được thêm, xoá hoặc thay đổi. Sau đó, hệ thống sẽ sử dụng một thuật toán có tên là Eugene W. Thuật toán chênh lệch của Myers để tìm ra số lượng thay đổi tối thiểu cần thực hiện từ danh sách cũ để tạo ra danh sách mới.

Sau khi DiffUtil xác định được những thay đổi, RecyclerView có thể sử dụng thông tin đó để chỉ cập nhật những mục đã thay đổi, được thêm, bị xoá hoặc di chuyển. Điều này hiệu quả hơn nhiều so với việc làm lại toàn bộ danh sách.

Trong nhiệm vụ này, bạn sẽ nâng cấp SleepNightAdapter để dùng DiffUtil nhằm tối ưu hoá RecyclerView cho các thay đổi đối với dữ liệu.

Bước 1: Triển khai SleepNightDiffCallback

Để sử dụng chức năng của lớp DiffUtil, hãy mở rộng DiffUtil.ItemCallback.

  1. Mở SleepNightAdapter.kt.
  2. Bên dưới định nghĩa đầy đủ về lớp cho SleepNightAdapter, hãy tạo một lớp cấp cao mới có tên là SleepNightDiffCallback, giúp mở rộng DiffUtil.ItemCallback. Truyền SleepNight dưới dạng tham số chung.
class SleepNightDiffCallback : DiffUtil.ItemCallback<SleepNight>() {
}
  1. Đặt con trỏ vào tên lớp SleepNightDiffCallback.
  2. Nhấn Alt+Enter (Option+Enter trên máy Mac) rồi chọn Implement Members (Triển khai thành viên).
  3. Trong hộp thoại mở ra, hãy nhấp vào Shift + nhấp chuột trái để chọn các phương thức areItemsTheSame()areContentsTheSame(), sau đó nhấp vào OK.

    Thao tác này sẽ tạo các đoạn mã sơ khai bên trong SleepNightDiffCallback cho hai phương thức, như minh hoạ bên dưới. DiffUtil sử dụng 2 phương thức này để xác định cách danh sách và các mục đã thay đổi.
    override fun areItemsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

    override fun areContentsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }
  1. Trong areItemsTheSame(), hãy thay thế TODO bằng mã kiểm thử xem 2 mục SleepNight được truyền vào, oldItemnewItem, có giống nhau hay không. Nếu các mục có cùng nightId, thì đó là cùng một mục, vì vậy hãy trả về true. Nếu không, hãy trả về false. DiffUtil sử dụng kiểm thử này để giúp phát hiện xem một mục đã được thêm, xoá hay di chuyển.
override fun areItemsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
   return oldItem.nightId == newItem.nightId
}
  1. Bên trong areContentsTheSame(), hãy kiểm tra xem oldItemnewItem có chứa cùng một dữ liệu hay không, tức là liệu chúng có bằng nhau hay không. Thao tác kiểm tra tính bình đẳng này sẽ kiểm tra tất cả các trường, vì SleepNight là một lớp dữ liệu. Các lớp Data sẽ tự động xác định equals và một số phương thức khác cho bạn. Nếu có sự khác biệt giữa oldItemnewItem, mã này sẽ cho DiffUtil biết rằng mặt hàng đã được cập nhật.
override fun areContentsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
   return oldItem == newItem
}

Đây là một mẫu hình phổ biến khi dùng RecyclerView để hiển thị một danh sách có thay đổi. RecyclerView cung cấp một lớp bộ chuyển đổi, ListAdapter, giúp bạn tạo một bộ chuyển đổi RecyclerView được hỗ trợ bởi một danh sách.

ListAdapter theo dõi danh sách cho bạn và thông báo cho bộ chuyển đổi khi danh sách được cập nhật.

Bước 1: Thay đổi bộ chuyển đổi để mở rộng ListAdapter

  1. Trong tệp SleepNightAdapter.kt, hãy thay đổi chữ ký lớp của SleepNightAdapter để mở rộng ListAdapter.
  2. Nếu được nhắc, hãy nhập androidx.recyclerview.widget.ListAdapter.
  3. Thêm SleepNight làm đối số đầu tiên vào ListAdapter, trước SleepNightAdapter.ViewHolder.
  4. Thêm SleepNightDiffCallback() làm tham số vào hàm khởi tạo. ListAdapter sẽ sử dụng tham số này để xác định thay đổi trong danh sách. Chữ ký lớp SleepNightAdapter đã hoàn tất của bạn sẽ có dạng như dưới đây.
class SleepNightAdapter : ListAdapter<SleepNight, SleepNightAdapter.ViewHolder>(SleepNightDiffCallback()) {
  1. Bên trong lớp SleepNightAdapter, hãy xoá trường data, bao gồm cả phương thức thiết lập. Bạn không cần đến nó nữa vì ListAdapter sẽ theo dõi danh sách cho bạn.
  2. Xoá chế độ ghi đè getItemCount(), vì ListAdapter sẽ triển khai phương thức này cho bạn.
  3. Để loại bỏ lỗi trong onBindViewHolder(), hãy thay đổi biến item. Thay vì dùng data để lấy item, hãy gọi phương thức getItem(position)ListAdapter cung cấp.
val item = getItem(position)

Bước 2: Sử dụng submitList() để cập nhật danh sách

Mã của bạn cần cho ListAdapter biết khi có danh sách đã thay đổi. ListAdapter cung cấp một phương thức có tên là submitList() để cho ListAdapter biết rằng có một phiên bản mới của danh sách. Khi phương thức này được gọi, ListAdapter sẽ so sánh danh sách mới với danh sách cũ và phát hiện các mục đã được thêm, xoá, di chuyển hoặc thay đổi. Sau đó, ListAdapter sẽ cập nhật các mục mà RecyclerView hiển thị.

  1. Mở SleepTrackerFragment.kt.
  2. Trong onCreateView(), trong trình theo dõi trên sleepTrackerViewModel, hãy tìm lỗi mà biến data bạn đã xoá được tham chiếu.
  3. Thay thế adapter.data = it bằng một lệnh gọi đến adapter.submitList(it). Mã đã cập nhật được trình bày bên dưới.

sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   it?.let {
       adapter.submitList(it)
   }
})
  1. Chạy ứng dụng. Ứng dụng sẽ chạy nhanh hơn, có thể không đáng kể nếu danh sách của bạn nhỏ.

Trong nhiệm vụ này, bạn sẽ sử dụng cùng một kỹ thuật như trong các lớp học lập trình trước để thiết lập liên kết dữ liệu và loại bỏ các lệnh gọi đến findViewById().

Bước 1: Thêm liên kết dữ liệu vào tệp bố cục

  1. Mở tệp bố cục list_item_sleep_night.xml trong thẻ Văn bản.
  2. Đặt con trỏ lên thẻ ConstraintLayout rồi nhấn Alt+Enter (Option+Enter trên máy Mac). Trình đơn ý định (trình đơn "khắc phục nhanh") sẽ mở ra.
  3. Chọn Chuyển đổi thành bố cục liên kết dữ liệu. Thao tác này sẽ bao bọc bố cục trong <layout> và thêm một thẻ <data> vào bên trong.
  4. Nếu cần, hãy di chuyển lên đầu và bên trong thẻ <data>, hãy khai báo một biến có tên là sleep.
  5. Đặt type thành tên đủ điều kiện của SleepNight, com.example.android.trackmysleepquality.database.SleepNight. Thẻ <data> đã hoàn tất của bạn sẽ có dạng như dưới đây.
   <data>
        <variable
            name="sleep"
            type="com.example.android.trackmysleepquality.database.SleepNight"/>
    </data>
  1. Để buộc tạo đối tượng Binding, hãy chọn Build > Clean Project (Xây dựng > Dọn dự án), sau đó chọn Build > Rebuild Project (Xây dựng > Tạo lại dự án). (Nếu bạn vẫn gặp vấn đề, hãy chọn File > Invalidate Caches / Restart (Tệp > Xoá bộ nhớ đệm/Khởi động lại).) Đối tượng liên kết ListItemSleepNightBinding, cùng với mã liên quan, sẽ được thêm vào các tệp đã tạo của dự án.

Bước 2: Tăng kích thước bố cục mục bằng cách sử dụng tính năng liên kết dữ liệu

  1. Mở SleepNightAdapter.kt.
  2. Trong lớp ViewHolder, hãy tìm phương thức from().
  3. Xoá khai báo của biến view.

Mã cần xoá:

val view = layoutInflater
       .inflate(R.layout.list_item_sleep_night, parent, false)
  1. Xác định một biến mới có tên là binding tại vị trí của biến view. Biến này sẽ mở rộng đối tượng liên kết ListItemSleepNightBinding, như minh hoạ bên dưới. Nhập đối tượng liên kết cần thiết.
val binding =
ListItemSleepNightBinding.inflate(layoutInflater, parent, false)
  1. Ở cuối hàm, thay vì trả về view, hãy trả về binding.
return ViewHolder(binding)
  1. Để loại bỏ lỗi này, hãy đặt con trỏ lên từ binding. Nhấn Alt+Enter (Option+Enter trên máy Mac) để mở trình đơn ý định.
  1. Chọn Thay đổi loại tham số "itemView" của hàm khởi tạo chính của lớp "ViewHolder" thành "ListItemSleepNightBinding". Thao tác này sẽ cập nhật loại tham số của lớp ViewHolder.

  1. Di chuyển lên phần định nghĩa lớp của ViewHolder để xem thay đổi trong chữ ký. Bạn sẽ thấy lỗi cho itemView vì bạn đã thay đổi itemView thành binding trong phương thức from().

    Trong định nghĩa lớp ViewHolder, hãy nhấp chuột phải vào một trong các lần xuất hiện của itemView rồi chọn Refactor > Rename (Tái cấu trúc > Đổi tên). Đổi tên thành binding.
  2. Thêm tiền tố val vào tham số hàm khởi tạo binding để biến tham số này thành một thuộc tính.
  3. Trong lệnh gọi đến lớp mẹ RecyclerView.ViewHolder, hãy thay đổi tham số từ binding thành binding.root. Bạn cần truyền một Viewbinding.rootConstraintLayout gốc trong bố cục mục.
  4. Khai báo lớp đã hoàn tất của bạn sẽ có dạng như mã bên dưới.
class ViewHolder private constructor(val binding: ListItemSleepNightBinding) : RecyclerView.ViewHolder(binding.root){

Bạn cũng thấy một lỗi cho các lệnh gọi đến findViewById() và bạn sẽ khắc phục lỗi này ở bước tiếp theo.

Bước 3: Thay thế findViewById()

Giờ đây, bạn có thể cập nhật các thuộc tính sleepLength, qualityqualityImage để sử dụng đối tượng binding thay vì findViewById().

  1. Thay đổi quá trình khởi chạy sleepLength, qualityStringqualityImage để sử dụng các khung hiển thị của đối tượng binding, như minh hoạ bên dưới. Sau bước này, mã của bạn sẽ không còn lỗi nào nữa.
val sleepLength: TextView = binding.sleepLength
val quality: TextView = binding.qualityString
val qualityImage: ImageView = binding.qualityImage

Khi đã có đối tượng liên kết, bạn không cần xác định các thuộc tính sleepLength, qualityqualityImage nữa. DataBinding sẽ lưu các tìm kiếm vào bộ nhớ đệm, nên bạn không cần khai báo các thuộc tính này.

  1. Nhấp chuột phải vào tên tài sản sleepLength, qualityqualityImage. Chọn Refactor > Inline (Tái cấu trúc > Nội tuyến) hoặc nhấn Control+Command+N (Option+Command+N trên máy Mac).
  2. Chạy ứng dụng của bạn. (Bạn có thể cần Dọn dẹpTạo lại dự án nếu dự án đó có lỗi.)

Trong nhiệm vụ này, bạn sẽ nâng cấp ứng dụng để sử dụng tính năng liên kết dữ liệu với các phương thức điều hợp liên kết nhằm đặt dữ liệu trong các khung hiển thị.

Trong một lớp học lập trình trước, bạn đã dùng lớp Transformations để lấy LiveData và tạo các chuỗi được định dạng để hiển thị trong các khung hiển thị văn bản. Tuy nhiên, nếu cần liên kết các loại khác nhau hoặc các loại phức tạp, bạn có thể cung cấp các phương thức điều hợp liên kết để giúp tính năng liên kết dữ liệu sử dụng các loại đó. Bộ chuyển đổi liên kết là những bộ chuyển đổi lấy dữ liệu của bạn và điều chỉnh dữ liệu đó thành một thứ mà tính năng liên kết dữ liệu có thể dùng để liên kết một thành phần hiển thị, chẳng hạn như văn bản hoặc hình ảnh.

Bạn sẽ triển khai 3 bộ chuyển đổi liên kết, một cho hình ảnh chất lượng và một cho mỗi trường văn bản. Tóm lại, để khai báo một bộ chuyển đổi liên kết, bạn hãy xác định một phương thức nhận một mục và một khung hiển thị, rồi chú thích phương thức đó bằng @BindingAdapter. Trong phần nội dung của phương thức, bạn sẽ triển khai quá trình chuyển đổi. Trong Kotlin, bạn có thể viết một bộ chuyển đổi liên kết dưới dạng một hàm mở rộng trên lớp khung hiển thị nhận dữ liệu.

Bước 1: Tạo các bộ chuyển đổi liên kết

Xin lưu ý rằng bạn sẽ phải nhập một số lớp trong bước này và các lớp đó sẽ không được gọi riêng lẻ.

  1. Mở SleepNightAdapater.kt.
  2. Bên trong lớp ViewHolder, hãy tìm phương thức bind() và tự nhắc nhở bản thân về chức năng của phương thức này. Bạn sẽ lấy mã tính toán các giá trị cho binding.sleepLength, binding.qualitybinding.qualityImage, rồi sử dụng mã đó bên trong trình chuyển đổi. (Hiện tại, hãy giữ nguyên mã; bạn sẽ di chuyển mã này trong một bước sau.)
  3. Trong gói sleeptracker, hãy tạo và mở một tệp có tên là BindingUtils.kt.
  4. Khai báo một hàm mở rộng trên TextView, có tên là setSleepDurationFormatted và truyền vào một SleepNight. Hàm này sẽ là bộ chuyển đổi để tính toán và định dạng thời lượng ngủ.
fun TextView.setSleepDurationFormatted(item: SleepNight) {}
  1. Trong phần nội dung của setSleepDurationFormatted, hãy liên kết dữ liệu với khung hiển thị như bạn đã làm trong ViewHolder.bind(). Gọi convertDurationToFormatted() rồi đặt text của TextView thành văn bản được định dạng. (Vì đây là một hàm mở rộng trên TextView, nên bạn có thể truy cập trực tiếp vào thuộc tính text.)
text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, context.resources)
  1. Để cho biết về phương thức điều hợp liên kết này, hãy chú giải hàm bằng @BindingAdapter.
  2. Hàm này là trình chuyển đổi cho thuộc tính sleepDurationFormatted, vì vậy, hãy truyền sleepDurationFormatted làm đối số cho @BindingAdapter.
@BindingAdapter("sleepDurationFormatted")
  1. Bộ chuyển đổi thứ hai đặt chất lượng giấc ngủ dựa trên giá trị trong một đối tượng SleepNight. Tạo một hàm mở rộng có tên là setSleepQualityString() trên TextView rồi truyền SleepNight vào.
  2. Trong phần nội dung, hãy liên kết dữ liệu với khung hiển thị như bạn đã làm trong ViewHolder.bind(). Gọi convertNumericQualityToString và đặt text.
  3. Chú giải hàm này bằng @BindingAdapter("sleepQualityString").
@BindingAdapter("sleepQualityString")
fun TextView.setSleepQualityString(item: SleepNight) {
   text = convertNumericQualityToString(item.sleepQuality, context.resources)
}
  1. Phương thức điều hợp liên kết thứ ba sẽ đặt hình ảnh trên một thành phần hiển thị hình ảnh. Tạo hàm mở rộng trên ImageView, gọi setSleepImage và sử dụng mã từ ViewHolder.bind(), như minh hoạ bên dưới.
@BindingAdapter("sleepImage")
fun ImageView.setSleepImage(item: SleepNight) {
   setImageResource(when (item.sleepQuality) {
       0 -> R.drawable.ic_sleep_0
       1 -> R.drawable.ic_sleep_1
       2 -> R.drawable.ic_sleep_2
       3 -> R.drawable.ic_sleep_3
       4 -> R.drawable.ic_sleep_4
       5 -> R.drawable.ic_sleep_5
       else -> R.drawable.ic_sleep_active
   })
}

Bước 2: Cập nhật SleepNightAdapter

  1. Mở SleepNightAdapter.kt.
  2. Xoá mọi thứ trong phương thức bind(), vì giờ đây bạn có thể sử dụng tính năng liên kết dữ liệu và các bộ chuyển đổi mới để thực hiện công việc này.
fun bind(item: SleepNight) {
}
  1. Bên trong bind(), hãy chỉ định trạng thái ngủ cho item, vì bạn cần cho đối tượng liên kết biết về SleepNight mới.
binding.sleep = item
  1. Bên dưới dòng đó, hãy thêm binding.executePendingBindings(). Lệnh gọi này là một hoạt động tối ưu hoá yêu cầu liên kết dữ liệu thực thi ngay mọi liên kết đang chờ xử lý. Bạn nên gọi executePendingBindings() khi sử dụng bộ chuyển đổi liên kết trong RecyclerView, vì thao tác này có thể tăng tốc độ điều chỉnh kích thước các thành phần hiển thị một chút.
 binding.executePendingBindings()

Bước 3: Thêm các liên kết vào bố cục XML

  1. Mở list_item_sleep_night.xml.
  2. Trong ImageView, hãy thêm một thuộc tính app có cùng tên với phương thức điều hợp liên kết đặt hình ảnh. Truyền biến sleep vào, như minh hoạ bên dưới.

    Thuộc tính này tạo mối kết nối giữa khung hiển thị và đối tượng liên kết thông qua bộ điều hợp. Bất cứ khi nào sleepImage được tham chiếu, bộ chuyển đổi sẽ điều chỉnh dữ liệu từ SleepNight.
app:sleepImage="@{sleep}"
  1. Làm tương tự cho khung hiển thị văn bản sleep_lengthquality_string. Bất cứ khi nào sleepDurationFormatted hoặc sleepQualityString được tham chiếu, các bộ chuyển đổi sẽ điều chỉnh dữ liệu từ SleepNight.
app:sleepDurationFormatted="@{sleep}"
app:sleepQualityString="@{sleep}"
  1. Chạy ứng dụng của bạn. Ứng dụng sẽ hoạt động giống hệt như trước đây. Các bộ chuyển đổi liên kết sẽ đảm nhiệm mọi công việc định dạng và cập nhật các khung hiển thị khi dữ liệu thay đổi, đơn giản hoá ViewHolder và mang lại cấu trúc tốt hơn nhiều cho mã so với trước đây.

Bạn đã hiển thị cùng một danh sách cho một số bài tập gần đây. Đó là theo thiết kế, nhằm cho bạn thấy rằng giao diện Adapter cho phép bạn thiết kế mã theo nhiều cách khác nhau. Mã của bạn càng phức tạp thì việc thiết kế mã một cách hiệu quả càng trở nên quan trọng. Trong các ứng dụng phát hành công khai, những mẫu này và các mẫu khác được dùng với RecyclerView. Tất cả các mẫu đều hoạt động và mỗi mẫu đều có lợi ích riêng. Bạn chọn loại nào tuỳ thuộc vào nội dung bạn đang xây dựng.

Xin chúc mừng! Đến đây, bạn đã gần như nắm vững RecyclerView trên Android.

Dự án Android Studio: RecyclerViewDiffUtilDataBinding.

DiffUtil:

  • RecyclerView có một lớp gọi là DiffUtil dùng để tính toán sự khác biệt giữa hai danh sách.
  • DiffUtil có một lớp tên là ItemCallBack mà bạn mở rộng để tìm ra sự khác biệt giữa hai danh sách.
  • Trong lớp ItemCallback, bạn phải ghi đè các phương thức areItemsTheSame()areContentsTheSame().

ListAdapter:

  • Để quản lý một số danh sách miễn phí, bạn có thể dùng lớp ListAdapter thay vì RecyclerView.Adapter. Tuy nhiên, nếu sử dụng ListAdapter, bạn phải tự viết bộ chuyển đổi cho các bố cục khác. Đó là lý do lớp học lập trình này hướng dẫn bạn cách thực hiện.
  • Để mở trình đơn ý định trong Android Studio, hãy đặt con trỏ lên bất kỳ mục nào trong mã rồi nhấn Alt+Enter (Option+Enter trên máy Mac). Trình đơn này đặc biệt hữu ích khi tái cấu trúc mã và tạo các phần giữ chỗ để triển khai phương thức. Trình đơn này phụ thuộc vào bối cảnh, vì vậy bạn cần đặt con trỏ chính xác để có được trình đơn phù hợp.

Liên kết dữ liệu:

  • Sử dụng tính năng liên kết dữ liệu trong bố cục mục để liên kết dữ liệu với các khung hiển thị.

Bộ chuyển đổi liên kết:

  • Trước đây, bạn đã dùng Transformations để tạo chuỗi từ dữ liệu. Nếu cần liên kết dữ liệu thuộc nhiều loại hoặc loại phức tạp, hãy cung cấp các bộ chuyển đổi liên kết để giúp tính năng liên kết dữ liệu sử dụng các loại đó.
  • Để khai báo một phương thức điều hợp liên kết, hãy xác định một phương thức lấy một mục và một khung hiển thị, rồi chú thích phương thức đó bằng @BindingAdapter. Trong Kotlin, bạn có thể viết phương thức điều hợp liên kết dưới dạng một hàm mở rộng trên View. Truyền tên của thuộc tính mà lớp chuyển đổi điều chỉnh. Ví dụ:
@BindingAdapter("sleepDurationFormatted")
  • Trong bố cục XML, hãy đặt một thuộc tính app có cùng tên với trình liên kết bộ điều hợp. Truyền một biến có dữ liệu. Ví dụ:
.app:sleepDurationFormatted="@{sleep}"

Các khoá học của Udacity:

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

Tài nguyên khác:

Phần này liệt kê các bài tập về nhà cho học viên của lớp học lập trình này trong phạm vi khoá học có người hướng dẫn. Người hướng dẫn phải thực hiện các việc sau đây:

  • Giao bài tập về nhà nếu cần.
  • Trao đổi với học viên về 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 các đề xuất này ít hoặc nhiều tuỳ ý và nên giao cho học viên 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ự học các lớp học lập trình, hãy sử dụng những bài tập về nhà này để kiểm tra kiến thức của mình.

Trả lời các câu hỏi sau

Câu hỏi 1

Điều nào sau đây là cần thiết để sử dụng DiffUtil? Hãy chọn mọi câu trả lời phù hợp.

▢ Mở rộng lớp ItemCallBack.

▢ Ghi đè areItemsTheSame().

▢ Ghi đè areContentsTheSame().

▢ Sử dụng tính năng liên kết dữ liệu để theo dõi những điểm khác biệt giữa các mục.

Câu hỏi 2

Câu nào sau đây là đúng về bộ chuyển đổi liên kết?

▢ Trình điều hợp liên kết là một hàm được chú giải bằng @BindingAdapter.

▢ Khi dùng một trình chuyển đổi liên kết, bạn có thể tách riêng định dạng dữ liệu với chế độ xem chủ sở hữu.

▢ Bạn phải sử dụng RecyclerViewAdapter nếu muốn dùng các bộ chuyển đổi liên kết.

▢ Bộ chuyển đổi liên kết là một giải pháp hiệu quả khi bạn cần chuyển đổi dữ liệu phức tạp.

Câu hỏi 3

Khi nào bạn nên cân nhắc sử dụng Transformations thay vì bộ chuyển đổi liên kết? Hãy chọn mọi câu trả lời phù hợp.

▢ Dữ liệu của bạn rất đơn giản.

▢ Bạn đang định dạng một chuỗi.

▢ Danh sách của bạn rất dài.

ViewHolder của bạn chỉ chứa một khung hiển thị.

Bắt đầu bài học tiếp theo: 7.3: GridLayout với RecyclerView