Thư viện phân trang theo dõi trạng thái của các yêu cầu tải dữ liệu được phân trang và hiển thị thông qua lớp LoadState
.
Ứng dụng có thể đăng ký trình xử lý với PagingDataAdapter
để nhận thông tin về trạng thái hiện tại và cập nhật UI (giao diện người dùng) cho phù hợp. Bộ chuyển đổi cung cấp các trạng thái này vì các trạng thái đó đồng bộ với UI (giao diện người dùng).
Điều này có nghĩa là trình nghe của bạn nhận được thông tin cập nhật khi UI (giao diện người dùng) áp dụng chế độ tải trang.
Bạn có thể cung cấp tín hiệu LoadState
riêng biệt cho từng LoadType
và loại nguồn dữ liệu (PagingSource
hoặc RemoteMediator
). Đối tượng CombinedLoadStates
do trình nghe cung cấp cung cấp thông tin về trạng thái tải của tất cả tín hiệu này. Bạn có thể dùng thông tin chi tiết này để hiện các chỉ báo tải phù hợp cho người dùng.
Các trạng thái đang tải
Thư viện phân trang hiện trạng thái tải để sử dụng trong UI (giao diện người dùng) thông qua đối tượng LoadState
. Các đối tượng LoadState
có một trong ba dạng tuỳ thuộc vào trạng thái tải hiện tại:
- Nếu không có hoạt động tải nào đang hoạt động và không có lỗi, thì
LoadState
là đối tượngLoadState.NotLoading
. Lớp con này cũng bao gồm thuộc tínhendOfPaginationReached
, cho biết liệu đã đến cuối quá trình phân trang hay chưa. - Nếu có một hoạt động tải đang hoạt động, thì
LoadState
là đối tượngLoadState.Loading
. - Nếu xảy ra lỗi thì
LoadState
là đối tượngLoadState.Error
.
Có 2 cách để sử dụng LoadState
trong UI (giao diện người dùng): dùng trình nghe hoặc dùng bộ chuyển đổi danh sách đặc biệt để trình bày trạng thái tải ngay trong danh sách RecyclerView
.
Truy cập trạng thái đang tải bằng một trình nghe
Để nhận trạng thái đang tải cho mục đích sử dụng chung trong UI (giao diện người dùng) của bạn, hãy sử dụng luồng loadStateFlow
hoặc phương thức addLoadStateListener()
do PagingDataAdapter
cung cấp. Các cơ chế này cung cấp quyền truy cập vào đối tượng CombinedLoadStates
bao gồm thông tin về hành vi LoadState
cho từng loại tải.
Trong ví dụ sau, PagingDataAdapter
hiện các thành phần UI (giao diện người dùng) tuỳ thuộc vào trạng thái hiện tại của quá trình tải làm mới:
Kotlin
// Activities can use lifecycleScope directly, but Fragments should instead use // viewLifecycleOwner.lifecycleScope. lifecycleScope.launch { pagingAdapter.loadStateFlow.collectLatest { loadStates -> progressBar.isVisible = loadStates.refresh is LoadState.Loading retry.isVisible = loadState.refresh !is LoadState.Loading errorMsg.isVisible = loadState.refresh is LoadState.Error } }
Java
pagingAdapter.addLoadStateListener(loadStates -> { progressBar.setVisibility(loadStates.refresh instanceof LoadState.Loading ? View.VISIBLE : View.GONE); retry.setVisibility(loadStates.refresh instanceof LoadState.Loading ? View.GONE : View.VISIBLE); errorMsg.setVisibility(loadStates.refresh instanceof LoadState.Error ? View.VISIBLE : View.GONE); });
Java
pagingAdapter.addLoadStateListener(loadStates -> { progressBar.setVisibility(loadStates.refresh instanceof LoadState.Loading ? View.VISIBLE : View.GONE); retry.setVisibility(loadStates.refresh instanceof LoadState.Loading ? View.GONE : View.VISIBLE); errorMsg.setVisibility(loadStates.refresh instanceof LoadState.Error ? View.VISIBLE : View.GONE); });
Để biết thêm thông tin về CombinedLoadStates
, hãy xem Truy cập các thông tin bổ sung về trạng thái tải.
Trình bày trạng thái đang tải bằng một bộ chuyển đổi
Thư viện phân trang cung cấp một bộ chuyển đổi danh sách khác, có tên là LoadStateAdapter
nhằm mục đích trình bày trạng thái tải ngay trong danh sách hiện của dữ liệu được phân trang. Bộ chuyển đổi này cho phép truy cập vào trạng thái tải hiện tại của danh sách, bạn có thể chuyển trạng thái này đến chế độ xem chủ sở hữu tuỳ chỉnh hiện thông tin này.
Đầu tiên, hãy tạo một lớp chế độ xem chủ sở hữu giúp giữ lại các thông tin tham chiếu đến chế độ xem đang tải và lỗi trên màn hình. Tạo một hàm bind()
chấp nhận LoadState
dưới dạng tham số. Hàm này sẽ bật/tắt chế độ xem hiển thị dựa trên tham số trạng thái tải:
Kotlin
class LoadStateViewHolder( parent: ViewGroup, retry: () -> Unit ) : RecyclerView.ViewHolder( LayoutInflater.from(parent.context) .inflate(R.layout.load_state_item, parent, false) ) { private val binding = LoadStateItemBinding.bind(itemView) private val progressBar: ProgressBar = binding.progressBar private val errorMsg: TextView = binding.errorMsg private val retry: Button = binding.retryButton .also { it.setOnClickListener { retry() } } fun bind(loadState: LoadState) { if (loadState is LoadState.Error) { errorMsg.text = loadState.error.localizedMessage } progressBar.isVisible = loadState is LoadState.Loading retry.isVisible = loadState is LoadState.Error errorMsg.isVisible = loadState is LoadState.Error } }
Java
class LoadStateViewHolder extends RecyclerView.ViewHolder { private ProgressBar mProgressBar; private TextView mErrorMsg; private Button mRetry; LoadStateViewHolder( @NonNull ViewGroup parent, @NonNull View.OnClickListener retryCallback) { super(LayoutInflater.from(parent.getContext()) .inflate(R.layout.load_state_item, parent, false)); LoadStateItemBinding binding = LoadStateItemBinding.bind(itemView); mProgressBar = binding.progressBar; mErrorMsg = binding.errorMsg; mRetry = binding.retryButton; } public void bind(LoadState loadState) { if (loadState instanceof LoadState.Error) { LoadState.Error loadStateError = (LoadState.Error) loadState; mErrorMsg.setText(loadStateError.getError().getLocalizedMessage()); } mProgressBar.setVisibility(loadState instanceof LoadState.Loading ? View.VISIBLE : View.GONE); mRetry.setVisibility(loadState instanceof LoadState.Error ? View.VISIBLE : View.GONE); mErrorMsg.setVisibility(loadState instanceof LoadState.Error ? View.VISIBLE : View.GONE); } }
Java
class LoadStateViewHolder extends RecyclerView.ViewHolder { private ProgressBar mProgressBar; private TextView mErrorMsg; private Button mRetry; LoadStateViewHolder( @NonNull ViewGroup parent, @NonNull View.OnClickListener retryCallback) { super(LayoutInflater.from(parent.getContext()) .inflate(R.layout.load_state_item, parent, false)); LoadStateItemBinding binding = LoadStateItemBinding.bind(itemView); mProgressBar = binding.progressBar; mErrorMsg = binding.errorMsg; mRetry = binding.retryButton; } public void bind(LoadState loadState) { if (loadState instanceof LoadState.Error) { LoadState.Error loadStateError = (LoadState.Error) loadState; mErrorMsg.setText(loadStateError.getError().getLocalizedMessage()); } mProgressBar.setVisibility(loadState instanceof LoadState.Loading ? View.VISIBLE : View.GONE); mRetry.setVisibility(loadState instanceof LoadState.Error ? View.VISIBLE : View.GONE); mErrorMsg.setVisibility(loadState instanceof LoadState.Error ? View.VISIBLE : View.GONE); } }
Tiếp theo, hãy tạo một lớp triển khai LoadStateAdapter
, đồng thời xác định các phương thức onCreateViewHolder()
và onBindViewHolder()
. Các phương thức này tạo một bản sao của chế độ xem chủ sở hữu tuỳ chỉnh và ràng buộc trạng thái tải liên quan.
Kotlin
// Adapter that displays a loading spinner when // state is LoadState.Loading, and an error message and retry // button when state is LoadState.Error. class ExampleLoadStateAdapter( private val retry: () -> Unit ) : LoadStateAdapter<LoadStateViewHolder>() { override fun onCreateViewHolder( parent: ViewGroup, loadState: LoadState ) = LoadStateViewHolder(parent, retry) override fun onBindViewHolder( holder: LoadStateViewHolder, loadState: LoadState ) = holder.bind(loadState) }
Java
// Adapter that displays a loading spinner when // state is LoadState.Loading, and an error message and retry // button when state is LoadState.Error. class ExampleLoadStateAdapter extends LoadStateAdapter<LoadStateViewHolder> { private View.OnClickListener mRetryCallback; ExampleLoadStateAdapter(View.OnClickListener retryCallback) { mRetryCallback = retryCallback; } @NotNull @Override public LoadStateViewHolder onCreateViewHolder(@NotNull ViewGroup parent, @NotNull LoadState loadState) { return new LoadStateViewHolder(parent, mRetryCallback); } @Override public void onBindViewHolder(@NotNull LoadStateViewHolder holder, @NotNull LoadState loadState) { holder.bind(loadState); } }
Java
// Adapter that displays a loading spinner when // state is LoadState.Loading, and an error message and retry // button when state is LoadState.Error. class ExampleLoadStateAdapter extends LoadStateAdapter<LoadStateViewHolder> { private View.OnClickListener mRetryCallback; ExampleLoadStateAdapter(View.OnClickListener retryCallback) { mRetryCallback = retryCallback; } @NotNull @Override public LoadStateViewHolder onCreateViewHolder(@NotNull ViewGroup parent, @NotNull LoadState loadState) { return new LoadStateViewHolder(parent, mRetryCallback); } @Override public void onBindViewHolder(@NotNull LoadStateViewHolder holder, @NotNull LoadState loadState) { holder.bind(loadState); } }
Hiển thị trạng thái đang tải như một mục đầu trang hoặc chân trang
Để hiển thị tiến trình tải trong mục đầu trang và chân trang, hãy gọi phương thức withLoadStateHeaderAndFooter()
từ đối tượng PagingDataAdapter
của bạn:
Kotlin
pagingAdapter .withLoadStateHeaderAndFooter( header = ExampleLoadStateAdapter(adapter::retry), footer = ExampleLoadStateAdapter(adapter::retry) )
Java
pagingAdapter .withLoadStateHeaderAndFooter( new ExampleLoadStateAdapter(pagingAdapter::retry), new ExampleLoadStateAdapter(pagingAdapter::retry));
Java
pagingAdapter .withLoadStateHeaderAndFooter( new ExampleLoadStateAdapter(pagingAdapter::retry), new ExampleLoadStateAdapter(pagingAdapter::retry));
Thay vào đó, bạn có thể gọi withLoadStateHeader()
hoặc withLoadStateFooter()
nếu bạn muốn danh sách RecyclerView
hiện trạng thái tải chỉ trong mục đầu trang hoặc chỉ ở mục chân trang.
Truy cập các thông tin bổ sung về trạng thái đang tải
Đối tượng CombinedLoadStates
của PagingDataAdapter
cung cấp thông tin về các trạng thái tải cho quá trình triển khai PagingSource
và cũng cho quá trình triển khai RemoteMediator
của bạn (nếu có).
Để thuận tiện, bạn có thể sử dụng các thuộc tính refresh
, append
và prepend
của CombinedLoadStates
để truy cập vào đối tượng LoadState
cho loại tải phù hợp. Các thuộc tính này thường tuân theo trạng thái tải của quá trình triển khai RemoteMediator
(nếu có); nếu không, chúng chứa trạng thái tải phù hợp của quá trình triển khai PagingSource
. Để biết thêm thông tin chi tiết về logic cơ bản, hãy xem tài liệu tham khảo dành cho CombinedLoadStates
.
Kotlin
lifecycleScope.launch { pagingAdapter.loadStateFlow.collectLatest { loadStates -> // Observe refresh load state from RemoteMediator if present, or // from PagingSource otherwise. refreshLoadState: LoadState = loadStates.refresh // Observe prepend load state from RemoteMediator if present, or // from PagingSource otherwise. prependLoadState: LoadState = loadStates.prepend // Observe append load state from RemoteMediator if present, or // from PagingSource otherwise. appendLoadState: LoadState = loadStates.append } }
Java
pagingAdapter.addLoadStateListener(loadStates -> { // Observe refresh load state from RemoteMediator if present, or // from PagingSource otherwise. LoadState refreshLoadState = loadStates.refresh; // Observe prepend load state from RemoteMediator if present, or // from PagingSource otherwise. LoadState prependLoadState = loadStates.prepend; // Observe append load state from RemoteMediator if present, or // from PagingSource otherwise. LoadState appendLoadState = loadStates.append; });
Java
pagingAdapter.addLoadStateListener(loadStates -> { // Observe refresh load state from RemoteMediator if present, or // from PagingSource otherwise. LoadState refreshLoadState = loadStates.refresh; // Observe prepend load state from RemoteMediator if present, or // from PagingSource otherwise. LoadState prependLoadState = loadStates.prepend; // Observe append load state from RemoteMediator if present, or // from PagingSource otherwise. LoadState appendLoadState = loadStates.append; });
Tuy nhiên, bạn cần nhớ rằng chỉ các trạng thái tải PagingSource
mới được đồng bộ hoá với các bản cập nhật UI (giao diện người dùng). Vì các thuộc tính refresh
,append
và prepend
có thể lấy trạng thái tải từ PagingSource
hoặc RemoteMediator
, nên chúng sẽ không được đảm bảo đồng bộ với các bản cập nhật UI (giao diện người dùng). Điều này có thể gây ra các sự cố về UI (giao diện người dùng) khi quá trình tải xuất hiện để hoàn thành trước khi bất kỳ dữ liệu mới nào được thêm vào UI (giao diện người dùng).
Vì lý do này, các trình truy cập tiện lợi hoạt động tốt để hiện trạng thái tải trong mục đầu trang hoặc chân trang, nhưng đối với các trường hợp sử dụng khác, bạn có thể cần truy cập cụ thể vào trạng thái tải của PagingSource
hoặc RemoteMediator
. CombinedLoadStates
cung cấp các thuộc tính source
và mediator
cho mục đích này. Mỗi thuộc tính này đều hiển thị một đối tượng LoadStates
chứa các đối tượng LoadState
cho PagingSource
hoặc RemoteMediator
tương ứng:
Kotlin
lifecycleScope.launch { pagingAdapter.loadStateFlow.collectLatest { loadStates -> // Directly access the RemoteMediator refresh load state. mediatorRefreshLoadState: LoadState? = loadStates.mediator.refresh // Directly access the RemoteMediator append load state. mediatorAppendLoadState: LoadState? = loadStates.mediator.append // Directly access the RemoteMediator prepend load state. mediatorPrependLoadState: LoadState? = loadStates.mediator.prepend // Directly access the PagingSource refresh load state. sourceRefreshLoadState: LoadState = loadStates.source.refresh // Directly access the PagingSource append load state. sourceAppendLoadState: LoadState = loadStates.source.append // Directly access the PagingSource prepend load state. sourcePrependLoadState: LoadState = loadStates.source.prepend } }
Java
pagingAdapter.addLoadStateListener(loadStates -> { // Directly access the RemoteMediator refresh load state. LoadState mediatorRefreshLoadState = loadStates.mediator.refresh; // Directly access the RemoteMediator append load state. LoadState mediatorAppendLoadState = loadStates.mediator.append; // Directly access the RemoteMediator prepend load state. LoadState mediatorPrependLoadState = loadStates.mediator.prepend; // Directly access the PagingSource refresh load state. LoadState sourceRefreshLoadState = loadStates.source.refresh; // Directly access the PagingSource append load state. LoadState sourceAppendLoadState = loadStates.source.append; // Directly access the PagingSource prepend load state. LoadState sourcePrependLoadState = loadStates.source.prepend; });
Java
pagingAdapter.addLoadStateListener(loadStates -> { // Directly access the RemoteMediator refresh load state. LoadState mediatorRefreshLoadState = loadStates.mediator.refresh; // Directly access the RemoteMediator append load state. LoadState mediatorAppendLoadState = loadStates.mediator.append; // Directly access the RemoteMediator prepend load state. LoadState mediatorPrependLoadState = loadStates.mediator.prepend; // Directly access the PagingSource refresh load state. LoadState sourceRefreshLoadState = loadStates.source.refresh; // Directly access the PagingSource append load state. LoadState sourceAppendLoadState = loadStates.source.append; // Directly access the PagingSource prepend load state. LoadState sourcePrependLoadState = loadStates.source.prepend; });
Toán tử chuỗi trên LoadState
Vì đối tượng CombinedLoadStates
cung cấp quyền truy cập vào tất cả thay đổi trong trạng thái tải, nên điều quan trọng là phải lọc luồng trạng thái tải dựa trên các sự kiện cụ thể. Điều này đảm bảo rằng bạn cập nhật UI (giao diện người dùng) vào thời điểm thích hợp để tránh tình trạng gián đoạn và cập nhật UI (giao diện người dùng) không cần thiết.
Ví dụ: giả sử bạn muốn hiện chế độ xem trống nhưng chỉ sau khi quá trình tải dữ liệu ban đầu hoàn tất. Trường hợp sử dụng này yêu cầu bạn xác minh rằng quá trình tải làm mới dữ liệu đã bắt đầu, sau đó đợi trạng thái NotLoading
để xác nhận rằng quá trình làm mới đã hoàn tất. Bạn phải lọc ra tất cả tín hiệu ngoại trừ những tín hiệu bạn cần:
Kotlin
lifecycleScope.launchWhenCreated { adapter.loadStateFlow // Only emit when REFRESH LoadState for RemoteMediator changes. .distinctUntilChangedBy { it.refresh } // Only react to cases where REFRESH completes, such as NotLoading. .filter { it.refresh is LoadState.NotLoading } // Scroll to top is synchronous with UI updates, even if remote load was // triggered. .collect { binding.list.scrollToPosition(0) } }
Java
PublishSubject<CombinedLoadStates> subject = PublishSubject.create(); Disposable disposable = subject.distinctUntilChanged(CombinedLoadStates::getRefresh) .filter( combinedLoadStates -> combinedLoadStates.getRefresh() instanceof LoadState.NotLoading) .subscribe(combinedLoadStates -> binding.list.scrollToPosition(0)); pagingAdapter.addLoadStateListener(loadStates -> { subject.onNext(loadStates); });
Java
LiveData<CombinedLoadStates> liveData = new MutableLiveData<>(); LiveData<LoadState> refreshLiveData = Transformations.map(liveData, CombinedLoadStates::getRefresh); LiveData<LoadState> distinctLiveData = Transformations.distinctUntilChanged(refreshLiveData); distinctLiveData.observeForever(loadState -> { if (loadState instanceof LoadState.NotLoading) { binding.list.scrollToPosition(0); } });
Ví dụ này sẽ chờ cho đến khi hệ thống cập nhật trạng thái tải làm mới, nhưng chỉ kích hoạt khi trạng thái là NotLoading
. Điều này đảm bảo rằng quá trình làm mới từ xa đã hoàn tất trước khi cập nhật UI (giao diện người dùng).
API luồng giúp loại hoạt động này có thể thực hiện được. Ứng dụng của bạn có thể chỉ định các sự kiện tải cần thiết và xử lý dữ liệu mới khi đáp ứng các tiêu chí thích hợp.
Đề xuất cho bạn
- Lưu ý: văn bản có đường liên kết sẽ hiện khi JavaScript tắt
- Tải và hiện dữ liệu được phân trang
- Phân trang qua mạng và cơ sở dữ liệu
- Tổng quan về thư viện Paging