Biblioteka stronicowania śledzi stan żądań wczytania danych z podziałem na strony i udostępnia je za pomocą klasy LoadState
.
Aplikacja może zarejestrować detektor w elemencie PagingDataAdapter
, aby otrzymywać informacje o bieżącym stanie i odpowiednio aktualizować interfejs. Te stany są dostarczane z adaptera, ponieważ są synchroniczne z interfejsem użytkownika.
Oznacza to, że detektor będzie otrzymywać aktualizacje po zastosowaniu w interfejsie wczytania strony.
Dla każdego parametru LoadType
i typu źródła danych (PagingSource
lub RemoteMediator
) jest dostarczany oddzielny sygnał LoadState
. Obiekt CombinedLoadStates
dostarczany przez detektor dostarcza informacji o stanie wczytywania ze wszystkich tych sygnałów. Na podstawie tych szczegółowych informacji możesz wyświetlać użytkownikom odpowiednie wskaźniki wczytywania.
Wczytuję stany
Biblioteka stronicowania ujawnia stan wczytywania do użycia w interfejsie za pomocą obiektu LoadState
. Obiekty LoadState
przyjmują 1 z 3 postaci w zależności od bieżącego stanu wczytywania:
- Jeśli nie ma żadnej aktywnej operacji wczytywania ani błędu,
LoadState
jest obiektemLoadState.NotLoading
. Ta podklasa zawiera też właściwośćendOfPaginationReached
, która wskazuje, czy został osiągnięty koniec podziału na strony. - Jeśli występuje aktywna operacja wczytywania,
LoadState
jest obiektemLoadState.Loading
. - Jeśli wystąpi błąd,
LoadState
jest obiektemLoadState.Error
.
Z dyrektywy LoadState
można korzystać w interfejsie na 2 sposoby: za pomocą odbiornika lub specjalnego adaptera listy, aby prezentować stan wczytywania bezpośrednio na liście RecyclerView
.
Uzyskiwanie dostępu do stanu wczytywania za pomocą detektora
Aby uzyskać stan wczytywania do ogólnego użytku w interfejsie, użyj strumienia loadStateFlow
lub metody addLoadStateListener()
podanej przez PagingDataAdapter
. Mechanizmy te zapewniają dostęp do obiektu CombinedLoadStates
, który zawiera informacje o zachowaniu LoadState
w poszczególnych typach wczytywania.
W tym przykładzie PagingDataAdapter
wyświetla różne komponenty UI w zależności od bieżącego stanu wczytywania 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 właściwości CombinedLoadStates
znajdziesz w artykule Uzyskiwanie dostępu do dodatkowych informacji o stanie wczytywania.
Prezentowanie stanu wczytywania za pomocą adaptera
Biblioteka stronicowania zawiera kolejny adapter listy o nazwie LoadStateAdapter
, który umożliwia prezentowanie stanu wczytywania bezpośrednio na wyświetlanej liście danych z podziałem na strony. Zapewnia on dostęp do bieżącego stanu wczytywania listy, który możesz przekazać osobie odpowiedzialnej za widok niestandardowy, która wyświetla te informacje.
Najpierw utwórz klasę widoku danych, która zachowuje odwołania do widoków wczytywania i błędów na ekranie. Utwórz funkcję bind()
, która akceptuje LoadState
jako parametr. Ta funkcja powinna przełączać widoczność widoku w zależności od parametru stanu wczytywania:
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 LoadStateAdapter
, i zdefiniuj metody onCreateViewHolder()
oraz onBindViewHolder()
. Te metody tworzą instancję niestandardowego właściciela widoku i wiążą 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 wczytywania 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));
Możesz zamiast tego wywołać metodę withLoadStateHeader()
lub withLoadStateFooter()
, jeśli chcesz, by lista RecyclerView
wyświetlała stan wczytywania tylko w nagłówku lub tylko w stopce.
Dostęp do dodatkowych informacji o stanie wczytywania
Obiekt CombinedLoadStates
z PagingDataAdapter
zawiera informacje o stanach obciążenia Twojej implementacji PagingSource
oraz RemoteMediator
, jeśli taka istnieje.
Dla wygody możesz użyć właściwości refresh
, append
i prepend
z CombinedLoadStates
, aby uzyskać dostęp do obiektu LoadState
odpowiedniego typu wczytywania. Jeśli taka właściwość istnieje, właściwości te zazwyczaj przejmują stan wczytywania z implementacji RemoteMediator
. Jeśli nie, zawierają odpowiedni stan wczytywania z implementacji PagingSource
. Więcej informacji o podstawowej logice znajdziesz w dokumentacji referencyjnej 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 stany wczytywania PagingSource
będą synchroniczne wraz z aktualizacjami interfejsu. Usługi refresh
, append
i prepend
mogą potencjalnie przejąć stan wczytywania z PagingSource
lub RemoteMediator
, dlatego nie ma gwarancji, że będą one synchronizowane z aktualizacjami interfejsu. Może to powodować problemy z interfejsem – wczytywanie powinno zakończyć się przed dodaniem nowych danych do interfejsu.
Z tego względu wygodne metody dostępu dobrze sprawdzają się do wyświetlania stanu wczytywania w nagłówku lub stopce, ale w innych przypadkach dostęp do stanu wczytywania może mieć konkretnie PagingSource
lub RemoteMediator
. CombinedLoadStates
udostępnia do tego celu właściwości source
i mediator
. Każda z tych właściwości ujawnia 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; });
Operatory łańcucha w LoadState
Obiekt CombinedLoadStates
zapewnia dostęp do wszystkich zmian stanu obciążenia, dlatego ważne jest, aby filtrować strumień stanu obciążenia według konkretnych zdarzeń. Dzięki temu interfejs użytkownika aktualizuje się w odpowiednim momencie, co pozwala uniknąć zacinania się i niepotrzebnych aktualizacji.
Załóżmy na przykład, że chcesz wyświetlić pusty widok, ale dopiero po zakończeniu wstępnego wczytywania danych. Ten przypadek użycia wymaga sprawdzenia, czy rozpoczęło się odświeżanie danych, a następnie poczekać, aż odświeżenie za pomocą stanu NotLoading
potwierdzi, że się ono zakończyło. Musisz odfiltrować wszystkie sygnały oprócz tych, które są Ci potrzebne:
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); } });
Ten przykład czeka na aktualizację stanu wczytywania odświeżania, ale jest wywoływany tylko wtedy, gdy stan to NotLoading
. Dzięki temu zdalne odświeżanie zostanie całkowicie zakończone, zanim interfejs użytkownika zostanie zaktualizowany.
Interfejsy API strumieniowania umożliwiają taki rodzaj operacji. Aplikacja może określić wymagane zdarzenia wczytywania i obsługiwać nowe dane, gdy zostaną spełnione odpowiednie kryteria.
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy JavaScript jest wyłączony
- Wczytywanie i wyświetlanie danych z podziałem na strony
- Strona z sieci i bazy danych
- Omówienie biblioteki stronicowania