La bibliothèque Paging suit l'état des requêtes de chargement pour les données paginées et les expose via la classe LoadState
.
Votre application peut enregistrer un écouteur avec le PagingDataAdapter
pour recevoir des informations sur l'état actuel et mettre à jour l'interface utilisateur en conséquence. Ces états sont fournis par l'adaptateur, car ils coïncident avec les mises à jour de l'UI.
Cela signifie que votre écouteur reçoit des mises à jour lorsque le chargement de la page a été appliqué à l'UI.
Un signal LoadState
distinct est fourni pour chaque LoadType
et type de source de données (soit PagingSource
, soit RemoteMediator
). L'objet CombinedLoadStates
fourni par l'écouteur apporte des informations sur l'état de chargement de tous ces signaux. Vous pouvez utiliser ces informations détaillées pour afficher les indicateurs de chargement appropriés pour vos utilisateurs.
États de chargement
La bibliothèque Paging expose l'état de chargement en vue de son utilisation dans l'interface utilisateur via l'objet LoadState
. Les objets LoadState
peuvent prendre l'une des trois formes suivantes selon l'état de chargement actuel :
- S'il n'y a pas d'opération de chargement active ni d'erreur,
LoadState
est un objetLoadState.NotLoading
. Cette sous-classe inclut également la propriétéendOfPaginationReached
, qui indique si la fin de la pagination a été atteinte. - Si une opération de chargement est active,
LoadState
est un objetLoadState.Loading
. - En cas d'erreur,
LoadState
est un objetLoadState.Error
.
Il existe deux façons d'utiliser le LoadState
dans votre UI : utiliser un écouteur ou un adaptateur de liste spécial pour présenter l'état de chargement directement dans la liste RecyclerView
.
Accéder à l'état de chargement avec un écouteur
Pour obtenir l'état de chargement en vue d'une utilisation générale dans l'interface utilisateur, utilisez le flux loadStateFlow
ou la méthode addLoadStateListener()
fournie par votre PagingDataAdapter
. Ces mécanismes permettent d'accéder à un objet CombinedLoadStates
qui inclut des informations sur le comportement du LoadState
pour chaque type de chargement.
Dans l'exemple suivant, PagingDataAdapter
affiche différents composants d'interface utilisateur en fonction de l'état actuel du chargement de l'actualisation :
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); });
Pour en savoir plus sur CombinedLoadStates
, consultez Accéder aux informations supplémentaires sur l'état de chargement.
Présenter l'état de chargement avec un adaptateur
La bibliothèque Paging fournit un autre adaptateur de liste appelé LoadStateAdapter
, qui permet de présenter l'état de chargement directement dans la liste des données paginées. Cet adaptateur permet d'accéder à l'état de chargement actuel de la liste, que vous pouvez transmettre à un conteneur de vue personnalisé qui affiche les informations.
Tout d'abord, créez une classe de conteneur de vue qui conserve les références aux vues de chargement et d'erreur à l'écran. Créez une fonction bind()
qui accepte un paramètre LoadState
. Cette fonction doit activer/désactiver la visibilité de la vue en fonction du paramètre d'état de chargement :
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); } }
Ensuite, créez une classe qui implémente LoadStateAdapter
, puis définissez les méthodes onCreateViewHolder()
et onBindViewHolder()
. Ces méthodes créent une instance de votre conteneur de vue personnalisé et associent l'état de chargement associé.
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); } }
Afficher l'état de chargement sous la forme d'un en-tête ou d'un pied de page
Pour afficher la progression du chargement dans un en-tête et un pied de page, appelez la méthode withLoadStateHeaderAndFooter()
à partir de votre objet 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));
Vous pouvez appeler withLoadStateHeader()
ou withLoadStateFooter()
si vous souhaitez afficher la liste RecyclerView
dans l'en-tête ou dans le pied de page.
Accéder à des informations supplémentaires sur l'état de chargement
L'objet CombinedLoadStates
de PagingDataAdapter
fournit des informations sur les états de chargement de votre implémentation PagingSource
ainsi que de votre implémentation RemoteMediator
, le cas échéant.
Pour plus de commodité, vous pouvez utiliser les méthodes refresh
, append
et prepend
de CombinedLoadStates
pour accéder à un objet LoadState
correspondant au type de chargement approprié. Ces propriétés s'appliquent généralement à l'état de chargement de l'implémentation RemoteMediator
, le cas échéant. Sinon, ils contiennent l'état de chargement approprié de l'implémentation PagingSource
. Pour en savoir plus sur la logique sous-jacente, consultez la documentation de référence sur 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; });
Toutefois, il est important de noter que seuls les états de chargement PagingSource
coïncident avec les mises à jour de l'interface utilisateur. Étant donné que les propriétés refresh
, append
et prepend
peuvent récupérer l'état de chargement à partir de la PagingSource
ou du RemoteMediator
, il n'est pas garanti qu'elles coïncident avec les mises à jour de l'UI. Cela peut en effet entraîner des problèmes d'UI : le chargement se termine avant que les nouvelles données n'aient été ajoutées à l'interface utilisateur.
Pour cette raison, les accesseurs de commodité sont utiles pour afficher l'état de chargement dans un en-tête ou un pied de page. Pour d'autres cas d'utilisation, vous devrez peut-être accéder spécifiquement à l'état de chargement à partir de PagingSource
ou RemoteMediator
CombinedLoadStates
fournit les propriétés source
et mediator
à cette fin. Ces propriétés exposent chacune un objet LoadStates
contenant respectivement les objets LoadState
pour PagingSource
ou 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; });
Opérateurs de chaîne sur LoadState
Étant donné que l'objet CombinedLoadStates
fournit un accès à toutes les modifications de l'état de chargement, il est important de filtrer le flux de l'état de chargement en fonction d'événements spécifiques. Cela vous permet de mettre à jour votre UI au bon moment pour éviter le stuttering et les mises à jour inutiles.
Par exemple, supposons que vous souhaitiez afficher une vue vide, mais seulement une fois que le chargement initial des données est terminé. Ce cas d'utilisation nécessite de vérifier qu'une actualisation de données a commencé, puis d'attendre l'état NotLoading
pour confirmer que l'actualisation est terminée. Vous devez exclure tous les signaux, sauf ceux dont vous avez besoin :
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); } });
Dans cet exemple, le système attend que l'état du chargement de l'actualisation soit mis à jour, mais l'actualisation ne se déclenche que lorsque l'état est NotLoading
. Cela garantit que l'actualisation à distance est entièrement terminée avant toute mise à jour de l'interface utilisateur.
Les API de flux permettent ce type d'opération. Votre appli peut spécifier les événements de chargement dont elle a besoin et gérer les nouvelles données lorsque les critères appropriés sont respectés.
Recommandations personnalisées
- Remarque : Le texte du lien s'affiche lorsque JavaScript est désactivé
- Charger et afficher des données paginées
- Page du réseau et de la base de données
- Présentation de la bibliothèque Paging