Kết hợp nhiều adapter với MergeAdapter trong Android
Bài đăng này đã không được cập nhật trong 4 năm
MergeAdapter
là một class mới xuất hiện ở recyclerview:1.2.0-alpha02
, cho phép chúng ta có thể kết hợp tuần tự nhiều adapter
để hiển thị trong một RecyclerView
duy nhất. Điều này cho phép ta có thể đóng gói các adapter tốt hơn, thay vì kết hợp nhiều kiểu dữ liệu trong cùng một adapter, giúp các adapter có thể tập trung hơn vào nhiệm vụ cụ thể chúng cần xử lý cũng như tăng tính tái sử dụng.
Một use case có thể sử dụng làm ví dụ, là khi chúng ta cần hiển thị một danh sách, với trạng thái loading ở header hay footer: khi danh sách đang lấy dữ liệu từ network, ta sẽ hiển thị một ProgressBar; trong trường hợp lỗi, ta sẽ hiển thị dialog và button retry.
Giới thiệu về MergeAdapter
MergeAdapter
cho phép chúng ta hiển thị nội dung của nhiều adapter, theo cách nối tiếp nhau. Ví dụ, giả sử chúng ta có 3 adapter như sau:
val firstAdapter: FirstAdapter = …
val secondAdapter: SecondAdapter = …
val thirdAdapter: ThirdAdapter = …
val mergeAdapter = MergeAdapter(firstAdapter, secondAdapter,
thirdAdapter)
recyclerView.adapter = mergeAdapter
recyclerView
sẽ hiển thị các item từ 3 adapter trên một cách tuần tự.
Việc có nhiều adapter khác nhau cho phép chung ta có thể phân tách rõ ràng từng phần của danh sách. Ví dụ, nếu ta muốn hiển thị một header, ta không cần đưa logic liên quan tới việc hiển thị header vào cùng adapter hiển thị danh sách ở body mà thay vào đó, ta sẽ đưa nó ra một adapter riêng.
Hiển thị trạng thái load ở header hoặc footer
Header hoặc footer của chúng ta sẽ hiện thị trạng thái load hoặc thông báo lỗi. Khi danh sách hoàn thành việc load dữ liệu, header hoặc footer sẽ không hiển thị bất cứ thứ gì. Do đó, chúng sẽ hiển thị một danh sách với 0 hoặc 1 item, với adapter:
val mergeAdapter = MergeAdapter(headerAdapter, listAdapter,
footerAdapter)
recyclerView.adapter = mergeAdapter
Nếu cả header và footer sử dụng cùng một layout, ViewHolder
và UI logic (như là khi nào và các hiển thị ProgressBar), ta có thể cài đặt chỉ một class Adapter
và tạo 2 instance của nó: 1 cho header và 1 cho footer.
Bạn có thể tham khảo pull request sau với cài đặt hoàn chỉnh, bao gồm:
- Một
LoadState
, expose bởiViewModel
. - Một layout của trạng thái loading cho footer và header.
- Một
ViewHolder
cho header và footer. - Một
ListAdapter
hiện thị 0 hoặc 1 iteam dự trênLoadState
. Mỗi khiLoadState
thay đổi, chúng ta sẽ thông báo rằng item đó cần thay đổi, tức là được chèn vào hoặc xoá đi.
Tìm hiểu thêm về MergeAdapter
ViewHolders
Theo mặc định, mỗi adapter duy trì một số ViewHolder
của chúng, mà không được tái sử dụng giữa adapter. Nếu nhiều adapter hiển thị cùng ViewHolder
, chúng ta có thể sẽ muốn tái sử dụng các instance giữa chúng. Chúng ta có thể làm được điều này bằng cách khởi tạo MergeAdapter
với đối tượng MergeAdapter.Config
, với thuộc tính isolateViewTypes = false
. Bằng cách này, tất cả các adapter được kết hợp sẽ sử dụng chung một tập hợp ViewHolder. Trong trường hợp hiện thị trạng thái load ở header hay footer ở ví dụ này, cả 2 ViewHolder thực tế sẽ hiển thị cùng một nội dung nên ta có thể tái sử dụng chúng.
Để hỗ trợ nhiều kiểu ViewHodler
, ta cần cài đặt Adapter.getItemViewType
. Khi chúng ta tái sử dụng ViewHoder
, hãy chắc chắn rằng cùng một view type sẽ không trỏ tới các ViewHolder
khác nhau. Một trong những cách tốt nhất để thực hiện việc này là trả về layout ID cho view type.
class HeaderAdapter() : RecyclerView.Adapter<LoadingStateViewHolderHeaderViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return LoadingStateViewHolder(parent)
}
override fun getItemViewType(position: Int): Int {
return R.layout.list_loading
}
}
class FooterAdapter() : RecyclerView.Adapter<LoadingStateViewHolderFooterViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return LoadingStateViewHolder(parent)
}
override fun getItemViewType(position: Int): Int {
return R.layout.list_loading
}
}
Sử dụng stable ids
Thay vì sử dụng stable ids với notifyDataSetChange
, chúng ta nên sử dụng các event chỉ định cụ thể của adapter cung cấp nhiều thông tin hơn về việc dữ liệu được thay đổi cho RecyclerView
, từ đó giúp việc cập nhật UI trở nên hiệu quả hơn. Nếu ta đang sử dụng ListAdapter
, việc thông báo sự kiện đã được xử lý với sự trợ giúp của callback DiffUtil
. Nhưng nếu ta cần sử dụng stable ids, MergeAdapter.Config
cung cấp 3 cài đặt cho stable ids, bao gồm: NO_STABLE_IDS
, ISOLATED_STABLE_IDS
, và SHARED_STABLE_IDS
. 2 cài đặc sau yêu cầu chúng ta phải xử lý stable ids trong adapter. Bạn có thể xem thêm tài liệu về StableIdMode
để biết thêm chi tiết và cách chúng hoạt động.
Data changes notification
Khi một adapter trong MergeAdapter
gọi tới một trong những notify function của nó, MergeAdapter
sẽ tính toán các vị trí mới của item trước khi update RecyclerView
.
Nếu một adapter gọi tới Adapter.notifyDataSetChanged
, MergeAdapter
sẽ gọi tới Adapter.notifyDataSetChanged
, thay vì Adapter.notifyItemRangeChanged
. Thông thường, RecyclerView
sẽ tránh vọi tới phương thức Adapter.notifyItemRangeChanged
, mà sẽ ưu tiên cập nhật chi tiết hơn hoặc sử dụng một Adapter
thực hiện việc cập nhật tự động, như là ListAdapter
hoặc SortedList
.
Finding ViewHolder position
Có thể bạn đã từng sử dụng ViewHolder.getAdapterPosition
trước đây để get vị trí của một ViewHolder
trong adapter. Hiện tại, bởi ta đã kết hợp nhiều adapter với nhau,ta sẽ sử dụng ViewHolder.getBindingAdapterPosition
. Nếu ta muốn get adapter cuối cùng bind tới một ViewHoder
, trong trường hợp ta chia sẻ các ViewHolder
, thì sẽ sử dụng ViewHolder.getBindingAdapter
.
Tổng kết
Trên đây là một số chia sẻ của mình về MergeAdapter. Nếu bạn muốn hiển thị tuần tự nhiều kiểu dữ liệu trên một danh sách và tách biệt thành nhiều adapter, hãy thử sử dụng MergeAdapter
. Để có thể kiểm soát nhiều hơn về ViewHolder
hay stable ids, hãy sử dụng MergeAdapter.Config
.
Cảm ơn bạn đã dành thời gian đọc bài viết.
Tài liệu tham khảo: Merge adapters sequentially with MergeAdapter.
All rights reserved