Pojęcia i implementacja w Jetpack Compose
Biblioteka Paging śledzi stan żądań wczytywania danych podzielonych na strony i udostępnia go za pomocą klasy LoadState.
Aplikacja może zarejestrować detektor w PagingDataAdapter, aby otrzymywać informacje o bieżącym stanie i odpowiednio aktualizować interfejs. Te stany są dostarczane przez adapter, ponieważ są synchroniczne z interfejsem.
Oznacza to, że odbiorca otrzymuje aktualizacje, gdy wczytanie strony zostanie zastosowane w interfejsie.
Dla każdego typu LoadState sygnału
LoadType i źródła danych
(PagingSource lub
RemoteMediator) jest udostępniany osobny sygnał. Obiekt
CombinedLoadStates
udostępniany przez odbiornik zawiera informacje o stanie wczytywania
ze wszystkich tych sygnałów. Możesz użyć tych szczegółowych informacji, aby wyświetlać użytkownikom odpowiednie wskaźniki wczytywania.
Wczytuję stany
Biblioteka Paging udostępnia stan wczytywania do użycia w interfejsie za pomocą obiektu LoadState. LoadState obiekty przyjmują jedną z 3 form w zależności od bieżącego stanu wczytywania:
- Jeśli nie ma aktywnej operacji wczytywania i nie wystąpił żaden błąd,
LoadStatejest obiektemLoadState.NotLoading. Ta podklasa zawiera też właściwośćendOfPaginationReached, która wskazuje, czy osiągnięto koniec paginacji. - Jeśli trwa aktywna operacja wczytywania,
LoadStatejest obiektemLoadState.Loading. - Jeśli wystąpi błąd,
LoadStatejest obiektemLoadState.Error.
W interfejsie możesz używać LoadState na 2 sposoby: za pomocą odbiornika lub specjalnego adaptera listy, aby wyświetlać stan ładowania bezpośrednio na liście RecyclerView.
Dostęp do stanu wczytywania za pomocą detektora
Aby uzyskać stan wczytywania do ogólnego użytku w interfejsie, użyj strumienia loadStateFlow lub metody addLoadStateListener() udostępnianej przez PagingDataAdapter. Te mechanizmy zapewniają dostęp do obiektu CombinedLoadStates, który zawiera informacje o LoadState
zachowaniu w przypadku każdego typu wczytywania.
W poniższym przykładzie element PagingDataAdapter wyświetla różne komponenty interfejsu w zależności od bieżącego stanu ładowania odświeżania:
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); });
Więcej informacji o CombinedLoadStates znajdziesz w sekcji Dostęp do dodatkowych informacji o stanie wczytywania.
Wyświetlanie stanu wczytywania za pomocą komponentu adaptera
Biblioteka Paging udostępnia kolejny adapter listy o nazwie
LoadStateAdapter, który służy do prezentowania stanu ładowania bezpośrednio na wyświetlanej liście danych podzielonych na strony. Ten adapter zapewnia dostęp do bieżącego stanu wczytywania listy, który możesz przekazać do niestandardowego obiektu ViewHolder wyświetlającego informacje.
Najpierw utwórz klasę uchwytu widoku, która będzie przechowywać odwołania do widoków ładowania i błędów na ekranie. Utwórz funkcję bind(), która akceptuje LoadState jako parametr. Ta funkcja powinna przełączać widoczność widoku na podstawie parametru loadState:
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); } }
Następnie utwórz klasę, która implementuje interfejs LoadStateAdapter, i zdefiniuj metody onCreateViewHolder() i onBindViewHolder(). Te metody tworzą instancję niestandardowego uchwytu widoku i wiążą z nią powiązany stan wczytywania.
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); } }
Wyświetlanie stanu ładowania jako nagłówka lub stopki
Aby wyświetlić postęp wczytywania w nagłówku i stopce, wywołaj metodę
withLoadStateHeaderAndFooter()
z obiektu 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));
Zamiast tego możesz wywołać funkcję
withLoadStateHeader()
lub
withLoadStateFooter()
, jeśli chcesz, aby lista RecyclerView wyświetlała stan ładowania tylko w nagłówku lub tylko w stopce.
Dostęp do dodatkowych informacji o stanie wczytywania
Obiekt CombinedLoadStates z PagingDataAdapter zawiera informacje o stanach wczytywania w przypadku implementacji PagingSource, a także implementacji RemoteMediator, jeśli taka istnieje.
Aby ułatwić sobie pracę, możesz użyć właściwości refresh, append i prepend z CombinedLoadStates, aby uzyskać dostęp do obiektu LoadState dla odpowiedniego typu wczytywania. Te właściwości zwykle odwołują się do stanu wczytywania z implementacji RemoteMediator, jeśli taka istnieje. W przeciwnym razie zawierają odpowiedni stan wczytywania z implementacji PagingSource. Szczegółowe informacje o logice działania znajdziesz w dokumentacji referencyjnej dotyczącej 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; });
Pamiętaj jednak, że tylko PagingSourcestany wczytywaniaPagingSource są synchronizowane z aktualizacjami interfejsu. Ponieważ właściwości refresh, append i prepend mogą potencjalnie przyjmować stan wczytywania z PagingSource lub RemoteMediator, nie gwarantuje się, że będą one zsynchronizowane z aktualizacjami interfejsu. Może to powodować problemy z interfejsem, w którym ładowanie wydaje się kończyć, zanim do interfejsu zostaną dodane nowe dane.
Z tego powodu wygodne akcesory dobrze sprawdzają się do wyświetlania stanu ładowania w nagłówku lub stopce, ale w innych przypadkach może być konieczne uzyskanie dostępu do stanu ładowania z PagingSource lub RemoteMediator. CombinedLoadStates udostępnia w tym celu właściwości source i mediator. Każda z tych właściwości udostępnia obiekt LoadStates, który zawiera obiekty LoadState dla PagingSource lub 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; });
Operatorzy łańcuchowi w LoadState
Ponieważ obiekt CombinedLoadStates zapewnia dostęp do wszystkich zmian stanu wczytywania, ważne jest, aby filtrować strumień stanu wczytywania na podstawie konkretnych zdarzeń. Dzięki temu zaktualizujesz interfejs w odpowiednim momencie, aby uniknąć zacięć i niepotrzebnych aktualizacji.
Załóżmy na przykład, że chcesz wyświetlić pusty widok, ale dopiero po zakończeniu początkowego wczytywania danych. Ten przypadek użycia wymaga sprawdzenia, czy rozpoczęło się odświeżanie danych, a następnie poczekania na stan NotLoading, aby potwierdzić zakończenie odświeżania. Musisz odfiltrować wszystkie sygnały z wyjątkiem tych, których potrzebujesz:
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); } });
W tym przykładzie oczekujemy na zaktualizowanie stanu wczytywania odświeżania, ale wywołujemy go tylko wtedy, gdy stan to NotLoading. Dzięki temu zdalne odświeżanie zostanie w pełni zakończone przed wprowadzeniem jakichkolwiek zmian w interfejsie.
Interfejsy Stream API umożliwiają tego typu operacje. Aplikacja może określać zdarzenia wczytywania, których potrzebuje, i obsługiwać nowe dane, gdy zostaną spełnione odpowiednie kryteria.
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy język JavaScript jest wyłączony.
- Wczytywanie i wyświetlanie danych podzielonych na strony
- Strona z sieci i bazy danych
- Omówienie biblioteki Paging