Concetti e implementazione di Jetpack Compose
La libreria Paging tiene traccia dello stato delle richieste di caricamento dei dati paginati ed espone
it tramite la LoadState classe.
La tua app può registrare un listener con il
PagingDataAdapter per
ricevere informazioni sullo stato attuale e aggiornare l'UI di conseguenza. Questi stati vengono forniti dall'adattatore perché sono sincroni con l'UI.
Ciò significa che il listener riceve gli aggiornamenti quando il caricamento pagina è stato applicato all'UI.
Viene fornito un segnale LoadState separato per ogni
LoadType e tipo di origine dati
(either PagingSource or
RemoteMediator). L'
CombinedLoadStates
oggetto fornito dal listener fornisce informazioni sullo stato di caricamento
da tutti questi segnali. Puoi utilizzare queste informazioni dettagliate per mostrare agli utenti gli indicatori di caricamento appropriati.
Stati di caricamento
La libreria Paging espone lo stato di caricamento per l'utilizzo nell'UI tramite l'oggetto LoadState. Gli oggetti LoadState assumono una delle tre forme seguenti, a seconda dello stato di caricamento attuale:
- Se non è presente alcuna operazione di caricamento attiva e nessun errore,
LoadStateè unLoadState.NotLoadingoggetto. Questa sottoclasse include anche laendOfPaginationReachedproprietà, che indica se è stata raggiunta la fine della paginazione. - Se è presente un'operazione di caricamento attiva,
LoadStateè unLoadState.Loadingoggetto. - Se si verifica un errore,
LoadStateè unLoadState.Erroroggetto.
Esistono due modi per utilizzare LoadState nell'UI: utilizzando un listener o un
adattatore di elenco speciale per presentare lo stato di caricamento direttamente nell'
RecyclerView
elenco.
Accedere allo stato di caricamento con un listener
Per ottenere lo stato di caricamento per l'utilizzo generale nell'UI, utilizza lo
loadStateFlow
stream o il
addLoadStateListener()
metodo fornito da PagingDataAdapter. Questi meccanismi forniscono l'accesso a
un CombinedLoadStates oggetto che include informazioni sul LoadState
comportamento per ogni tipo di caricamento.
Nell'esempio seguente, PagingDataAdapter mostra componenti UI diversi a seconda dello stato attuale del caricamento di aggiornamento:
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); });
Per ulteriori informazioni su CombinedLoadStates, consulta Accedere a informazioni aggiuntive sullo stato di caricamento.
Presentare lo stato di caricamento con un adattatore
La libreria Paging fornisce un altro adattatore di elenco chiamato
LoadStateAdapter per lo
scopo di presentare lo stato di caricamento direttamente nell'elenco visualizzato dei dati paginati. Questo adattatore fornisce l'accesso allo stato di caricamento attuale dell'elenco, che puoi passare a un titolare della visualizzazione personalizzato che visualizza le informazioni.
Innanzitutto, crea una classe di titolare della visualizzazione che mantenga i riferimenti alle visualizzazioni di caricamento ed errore sullo schermo. Crea una funzione bind() che accetta un LoadState come parametro. Questa funzione deve attivare/disattivare la visibilità della visualizzazione in base al parametro dello stato di caricamento:
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); } }
Poi, crea una classe che implementa LoadStateAdapter e definisci i
onCreateViewHolder()
e i
onBindViewHolder()
metodi. Questi metodi creano un'istanza del titolare della visualizzazione personalizzato e associano lo stato di caricamento associato.
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); } }
Visualizzare lo stato di caricamento come intestazione o piè di pagina
Per visualizzare l'avanzamento del caricamento in un'intestazione e un piè di pagina, chiama il
withLoadStateHeaderAndFooter()
metodo dall'oggetto PagingDataAdapter:
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));
In alternativa, puoi chiamare
withLoadStateHeader()
o
withLoadStateFooter()
se vuoi che l'elenco RecyclerView mostri lo stato di caricamento solo nell'
intestazione o solo nel piè di pagina.
Accedere a informazioni aggiuntive sullo stato di caricamento
L'oggetto CombinedLoadStates di PagingDataAdapter fornisce informazioni sugli stati di caricamento per l'implementazione di PagingSource e per l'implementazione di RemoteMediator, se esistente.
Per comodità, puoi utilizzare le
refresh,
append e
prepend
proprietà di CombinedLoadStates per accedere a un oggetto LoadState per il
tipo di caricamento appropriato. Queste proprietà in genere rimandano allo stato di caricamento dell'implementazione di RemoteMediator, se esistente; in caso contrario, contengono lo stato di caricamento appropriato dell'implementazione di PagingSource. Per informazioni più dettagliate
sulla logica sottostante, consulta la documentazione di riferimento per
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; });
Tuttavia, è importante ricordare che solo gli stati di caricamento di PagingSource sono garantiti per essere sincroni con gli aggiornamenti dell'UI. Poiché le proprietà refresh, append e prepend possono potenzialmente assumere lo stato di caricamento da PagingSource o RemoteMediator, non è garantito che siano sincrone con gli aggiornamenti dell'UI. Ciò può causare problemi dell'UI in cui il caricamento sembra terminare prima che i nuovi dati vengano aggiunti all'UI.
Per questo motivo, gli accessor di convenienza funzionano bene per visualizzare lo stato di caricamento in un'intestazione o un piè di pagina, ma per altri casi d'uso potrebbe essere necessario accedere in modo specifico allo stato di caricamento da PagingSource o RemoteMediator. CombinedLoadStates fornisce le
source e
mediator
proprietà a questo scopo. Queste proprietà espongono ciascuna un
LoadStates oggetto che
contiene gli oggetti LoadState per PagingSource o RemoteMediator
rispettivamente:
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; });
Operatori di concatenamento su LoadState
Poiché l'oggetto CombinedLoadStates fornisce l'accesso a tutte le modifiche dello stato di caricamento, è importante filtrare lo stream dello stato di caricamento in base a eventi specifici. In questo modo, l'UI viene aggiornata al momento opportuno per evitare interruzioni e aggiornamenti non necessari.
Supponiamo, ad esempio, di voler visualizzare una visualizzazione vuota, ma solo dopo il completamento del caricamento iniziale dei dati. Questo caso d'uso richiede di verificare che sia stato avviato un caricamento di aggiornamento dei dati, quindi attendere lo stato NotLoading per confermare che l'aggiornamento è stato completato. Devi filtrare tutti i segnali tranne quelli di cui hai bisogno:
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); } });
Questo esempio attende l'aggiornamento dello stato di caricamento dell'aggiornamento, ma si attiva solo quando lo stato è NotLoading. In questo modo, l'aggiornamento remoto viene completato prima che vengano apportati aggiornamenti all'UI.
Le API di streaming rendono possibile questo tipo di operazione. La tua app può specificare gli eventi di caricamento di cui ha bisogno e gestire i nuovi dati quando vengono soddisfatti i criteri appropriati.
Consigliati per te
- Nota: il testo del link viene visualizzato quando JavaScript è disattivato
- Caricare e visualizzare dati paginati
- Paginare da rete e database
- Panoramica della libreria Paging