Libary Paging melacak status permintaan pemuatan untuk data yang di-page dan menampilkannya
melalui class LoadState
.
Aplikasi Anda dapat mendaftarkan pemroses dengan
PagingDataAdapter
untuk
menerima informasi tentang status saat ini dan mengupdate UI sebagaimana mestinya. Status
ini disediakan dari adaptor karena status tersebut sinkron dengan UI.
Ini berarti bahwa pemroses Anda menerima update saat pemuatan halaman telah
diterapkan ke UI.
Sinyal LoadState
terpisah diberikan untuk setiapLoadType
dan jenis sumber data
(PagingSource
atau
RemoteMediator
). Objek
CombinedLoadStates
yang diberikan oleh pemroses berisi informasi tentang status pemuatan
dari semua sinyal ini. Anda dapat menggunakan informasi mendetail ini untuk menampilkan
indikator pemuatan yang sesuai untuk pengguna Anda.
Status pemuatan
Library Paging menampilkan status pemuatan untuk digunakan di UI melalui
objek LoadState
. Objek LoadState
mengambil salah satu dari tiga bentuk, bergantung pada
status pemuatan saat ini:
- Jika tidak ada operasi pemuatan yang aktif dan tidak ada error,
LoadState
adalah objekLoadState.NotLoading
. Subclass ini juga menyertakan propertiendOfPaginationReached
, yang menunjukkan apakah akhir penomoran halaman telah tercapai. - Jika ada operasi pemuatan yang aktif,
LoadState
adalah objekLoadState.Loading
. - Jika ada error,
LoadState
adalah objekLoadState.Error
.
Ada dua cara untuk menggunakan LoadState
di UI Anda: menggunakan pemroses, atau menggunakan
adaptor daftar khusus untuk menyajikan status pemuatan langsung dalam
daftar
RecyclerView
.
Mengakses status pemuatan dengan pemroses
Untuk mendapatkan status pemuatan bagi penggunaan umum di UI Anda, gunakan
aliran loadStateFlow
atau metode
addLoadStateListener()
yang disediakan oleh PagingDataAdapter
Anda. Mekanisme ini menyediakan akses ke
objek CombinedLoadStates
yang menyertakan informasi tentang perilaku LoadState
untuk setiap jenis pemuatan.
Dalam contoh berikut, PagingDataAdapter
menampilkan komponen UI
yang berbeda, bergantung pada status pemuatan refresh saat ini:
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); });
Untuk informasi selengkapnya tentang CombinedLoadStates
, baca Mengakses informasi status
pemuatan tambahan.
Menampilkan status pemuatan dengan adaptor
Library Paging menyediakan adaptor daftar lain yang disebut
LoadStateAdapter
untuk
menyajikan status pemuatan langsung dalam daftar data yang di-page
yang ditampilkan. Adaptor ini memberikan akses ke status pemuatan daftar saat ini, yang
dapat Anda teruskan ke holder tampilan kustom yang menampilkan informasi tersebut.
Pertama-tama, buat class holder tampilan yang menyimpan referensi ke tampilan pemuatan dan error
pada layar Anda. Buat fungsi bind()
yang menerima LoadState
sebagai
parameter. Fungsi ini harus mengalihkan visibilitas tampilan berdasarkan parameter
status pemuatan:
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); } }
Selanjutnya, buat class yang mengimplementasikan LoadStateAdapter
, dan tentukan
metode
onCreateViewHolder()
dan
onBindViewHolder()
. Metode tersebut membuat instance view holder kustom Anda dan mengikat
status pemuatan terkait.
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); } }
Menampilkan status pemuatan sebagai header atau footer
Untuk menampilkan proses pemuatan di header dan footer, panggil
metode withLoadStateHeaderAndFooter()
dari objek PagingDataAdapter
Anda.
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));
Sebagai gantinya, Anda dapat memanggil
withLoadStateHeader()
atau
withLoadStateFooter()
jika Anda ingin daftar RecyclerView
menampilkan status pemuatan hanya di
header atau hanya di footer.
Mengakses informasi status pemuatan tambahan
Objek CombinedLoadStates
dari PagingDataAdapter
menyediakan informasi tentang
status pemuatan untuk implementasi PagingSource
serta untuk implementasi
RemoteMediator
Anda, jika ada.
Agar mudah, Anda dapat menggunakan properti
refresh
,
append
, dan
prepend
dari CombinedLoadStates
guna mengakses objek LoadState
untuk
jenis pemuatan yang sesuai. Properti ini biasanya berbeda dari status pemuatan dari
implementasi RemoteMediator
, jika ada; jika tidak, properti tersebut berisi
status pemuatan yang sesuai dari implementasi PagingSource
. Untuk informasi lebih
mendetail tentang logika yang mendasarinya, baca dokumentasi referensi untuk
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; });
Namun, penting untuk diingat bahwa hanya status pemuatan PagingSource
yang dijamin akan sinkron dengan update UI. Karena properti refresh
,
append
, dan prepend
berpotensi dapat mengambil status pemuatan dari
PagingSource
atau RemoteMediator
, properti tersebut tidak dijamin akan
sinkron dengan update UI. Hal ini dapat menyebabkan masalah UI ketika pemuatan telah
selesai sebelum data baru apa pun ditambahkan ke UI tersebut.
Karena alasan ini, aksesor praktis berfungsi dengan baik untuk menampilkan status pemuatan
di header atau footer. Namun, untuk kasus penggunaan lain, Anda mungkin perlu
secara spesifik mengakses status pemuatan dari PagingSource
atau
RemoteMediator
. CombinedLoadStates
menyediakan properti
source
dan
mediator
untuk tujuan ini. Setiap properti ini menampilkan
objek LoadStates
yang
berisi objek LoadState
, masing-masing
untuk PagingSource
atau RemoteMediator
:
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; });
Operator rantai di LoadState
Karena objek CombinedLoadStates
memberikan akses ke semua perubahan di
status pemuatan, penting untuk memfilter aliran status pemuatan berdasarkan peristiwa
tertentu. Hal ini memastikan bahwa Anda mengupdate UI pada saat yang tepat untuk menghindari
update UI yang tersendat dan tidak penting.
Misalnya, anggaplah Anda ingin menampilkan tampilan kosong tetapi hanya setelah
pemuatan data awal selesai. Kasus penggunaan ini mengharuskan Anda memastikan bahwa pemuatan
refresh data telah dimulai, lalu tunggu status NotLoading
untuk mengonfirmasi bahwa
refresh tersebut telah selesai. Anda harus memfilter semua sinyal, kecuali yang
Anda perlukan:
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); } });
Contoh ini menunggu hingga status pemuatan refresh diupdate, tetapi hanya memicu
ketika statusnya NotLoading
. Cara ini memastikan bahwa refresh jarak jauh telah selesai
sepenuhnya sebelum update UI apa pun dilakukan.
Stream API yang memungkinkan jenis operasi ini. Aplikasi Anda dapat menentukan peristiwa pemuatan yang diperlukannya dan menangani data baru saat kriteria yang sesuai telah terpenuhi.
Direkomendasikan untuk Anda
- Catatan: teks link ditampilkan saat JavaScript nonaktif
- Memuat dan menampilkan data yang dibagi-bagi
- Halaman dari jaringan dan database
- Ringkasan library paging