Konzepte und Jetpack Compose-Implementierung
Die Paging-Bibliothek verfolgt den Status von Ladeanfragen für Seiten-Daten und stellt
ihn über die LoadState Klasse zur Verfügung.
Ihre App kann einen Listener mit dem
PagingDataAdapter registrieren, um
Informationen zum aktuellen Status zu erhalten und die UI entsprechend zu aktualisieren. Diese Status werden vom Adapter bereitgestellt, da sie synchron mit der UI sind.
Das bedeutet, dass Ihr Listener Aktualisierungen erhält, wenn der Seitenaufbau auf die UI angewendet wurde.
Für jedes separate LoadState Signal wird für jeden
LoadType und Datenquelltyp
(entweder PagingSource oder
RemoteMediator) bereitgestellt. Das
CombinedLoadStates
Objekt, das vom Listener bereitgestellt wird, enthält Informationen zum Ladestatus
aus all diesen Signalen. Anhand dieser detaillierten Informationen können Sie Ihren Nutzern die entsprechenden Ladeindikatoren anzeigen.
Ladestatus
Die Paging-Bibliothek stellt den Ladestatus für die Verwendung in der UI über das Objekt LoadState zur Verfügung. LoadState -Objekte haben je nach aktuellem Ladestatus eine von drei Formen:
- Wenn kein aktiver Ladevorgang und kein Fehler vorliegt, ist
LoadStateeinLoadState.NotLoadingObjekt. Diese Unterklasse enthält auch dieendOfPaginationReachedProperty, die angibt, ob das Ende der Paginierung erreicht wurde. - Wenn ein aktiver Ladevorgang vorliegt, ist
LoadStateeinLoadState.LoadingObjekt. - Wenn ein Fehler vorliegt, ist
LoadStateeinLoadState.Error-Objekt.
Es gibt zwei Möglichkeiten, LoadState in Ihrer UI zu verwenden: mit einem Listener oder mit einem
speziellen Listenadapter, um den Ladestatus direkt in der
RecyclerView
Liste darzustellen.
Auf den Ladestatus mit einem Listener zugreifen
Wenn Sie den Ladestatus für die allgemeine Verwendung in Ihrer UI abrufen möchten, verwenden Sie den
loadStateFlow
Stream oder die
addLoadStateListener()
Methode, die von Ihrem PagingDataAdapter bereitgestellt werden. Diese Mechanismen ermöglichen den Zugriff auf
ein CombinedLoadStates-Objekt, das Informationen zum LoadState
Verhalten für jeden Ladetyp enthält.
Im folgenden Beispiel zeigt PagingDataAdapter je nach aktuellem Status des Aktualisierungsladevorgangs verschiedene UI-Komponenten an:
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); });
Weitere Informationen zu CombinedLoadStates finden Sie unter Zusätzliche Informationen zum Ladestatus
abrufen.
Ladestatus mit einem Adapter präsentieren
Die Paging-Bibliothek bietet einen weiteren Listenadapter namens
LoadStateAdapter, mit dem der
Ladestatus direkt in der angezeigten Liste der Seiten-
Daten dargestellt werden kann. Dieser Adapter ermöglicht den Zugriff auf den aktuellen Ladestatus der Liste, den Sie an einen benutzerdefinierten View-Holder übergeben können, der die Informationen anzeigt.
Erstellen Sie zuerst eine View-Holder-Klasse, die Verweise auf die Lade- und Fehleransichten auf dem Bildschirm enthält. Erstellen Sie eine bind()-Funktion, die ein LoadState als Parameter akzeptiert. Diese Funktion sollte die Sichtbarkeit der Ansicht basierend auf dem Parameter für den Ladestatus umschalten:
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); } }
Erstellen Sie als Nächstes eine Klasse, die LoadStateAdapter implementiert, und definieren Sie die
onCreateViewHolder()
und
onBindViewHolder()
Methoden. Mit diesen Methoden wird eine Instanz Ihres benutzerdefinierten View-Holders erstellt und der zugehörige Ladestatus gebunden.
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); } }
Ladestatus als Header oder Footer anzeigen
Wenn Sie den Ladefortschritt in einem Header und einem Footer anzeigen möchten, rufen Sie die
withLoadStateHeaderAndFooter()
Methode aus Ihrem PagingDataAdapter Objekt auf:
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));
Sie können stattdessen
withLoadStateHeader()
oder
withLoadStateFooter()
aufrufen, wenn die RecyclerView Liste den Ladestatus nur im
Header oder nur im Footer anzeigen soll.
Zusätzliche Informationen zum Ladestatus abrufen
Das CombinedLoadStates-Objekt von PagingDataAdapter enthält Informationen zu den Ladestatus für Ihre PagingSource-Implementierung und auch für Ihre RemoteMediator-Implementierung, falls vorhanden.
Sie können die
refresh,
append und
prepend
Properties aus CombinedLoadStates verwenden, um auf ein LoadState Objekt für den
entsprechenden Ladetyp zuzugreifen. Diese Properties verweisen in der Regel auf den Ladestatus aus der RemoteMediator-Implementierung, falls vorhanden. Andernfalls enthalten sie den entsprechenden Ladestatus aus der PagingSource-Implementierung. Weitere Informationen zur zugrunde liegenden Logik finden Sie in der Referenzdokumentation zu
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; });
Es ist jedoch wichtig zu beachten, dass nur die PagingSource-Ladestatus garantiert synchron mit UI-Aktualisierungen sind. Da die Properties refresh, append und prepend den Ladestatus möglicherweise von PagingSource oder RemoteMediator übernehmen, sind sie nicht garantiert synchron mit UI-Aktualisierungen. Dies kann zu UI-Problemen führen, bei denen der Ladevorgang abgeschlossen zu sein scheint, bevor neue Daten zur UI hinzugefügt wurden.
Aus diesem Grund eignen sich die Convenience-Accessors gut, um den Ladestatus in einem Header oder Footer anzuzeigen. Für andere Anwendungsfälle müssen Sie jedoch möglicherweise speziell auf den Ladestatus von PagingSource oder RemoteMediator zugreifen. CombinedLoadStates bietet die
source und
mediator
Properties für diesen Zweck. Diese Properties stellen jeweils ein
LoadStates-Objekt zur Verfügung, das
die LoadState-Objekte für PagingSource bzw. RemoteMediator
enthält:
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; });
Operatoren für LoadState verketten
Da das CombinedLoadStates-Objekt Zugriff auf alle Änderungen des Ladestatus bietet, ist es wichtig, den Stream des Ladestatus anhand bestimmter Ereignisse zu filtern. So können Sie Ihre UI zum richtigen Zeitpunkt aktualisieren, um Ruckler und unnötige UI-Aktualisierungen zu vermeiden.
Angenommen, Sie möchten eine leere Ansicht anzeigen, aber erst nachdem der erste Ladevorgang abgeschlossen ist. In diesem Anwendungsfall müssen Sie prüfen, ob ein Datenaktualisierungsladevorgang gestartet wurde, und dann auf den Status NotLoading warten, um zu bestätigen, dass die Aktualisierung abgeschlossen ist. Sie müssen alle Signale herausfiltern, die Sie nicht benötigen:
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); } });
In diesem Beispiel wird gewartet, bis der Ladestatus der Aktualisierung aktualisiert wurde, aber nur ausgelöst, wenn der Status NotLoading ist. So wird sichergestellt, dass die Remote-Aktualisierung vollständig abgeschlossen ist, bevor UI-Aktualisierungen erfolgen.
Stream-APIs ermöglichen diese Art von Vorgang. Ihre App kann die benötigten Ladeereignisse angeben und die neuen Daten verarbeiten, wenn die entsprechenden Kriterien erfüllt sind.
Empfehlungen für Sie
- Hinweis: Linktext wird angezeigt, wenn JavaScript deaktiviert ist
- Seiten-Daten laden und anzeigen
- Paginierung aus Netzwerk und Datenbank
- Übersicht über die Paging-Bibliothek