Библиотека подкачки отслеживает состояние запросов на загрузку выгружаемых данных и предоставляет его через класс LoadState
. Ваше приложение может зарегистрировать прослушиватель с помощью PagingDataAdapter
, чтобы получать информацию о текущем состоянии и соответствующим образом обновлять пользовательский интерфейс. Эти состояния предоставляются адаптером, поскольку они синхронны с пользовательским интерфейсом. Это означает, что ваш прослушиватель получает обновления, когда загрузка страницы применяется к пользовательскому интерфейсу.
Отдельный сигнал LoadState
предоставляется для каждого LoadType
и типа источника данных ( PagingSource
или RemoteMediator
). Объект CombinedLoadStates
, предоставляемый прослушивателем, предоставляет информацию о состоянии загрузки на основе всех этих сигналов. Эту подробную информацию можно использовать для отображения пользователям соответствующих индикаторов загрузки.
Загрузка состояний
Библиотека подкачки предоставляет состояние загрузки для использования в пользовательском интерфейсе через объект LoadState
. Объекты LoadState
принимают одну из трех форм в зависимости от текущего состояния загрузки:
- Если нет активной операции загрузки и ошибок, то
LoadState
является объектомLoadState.NotLoading
. Этот подкласс также включает свойствоendOfPaginationReached
, которое указывает, достигнут ли конец нумерации страниц. - Если есть активная операция загрузки, то
LoadState
является объектомLoadState.Loading
. - Если возникла ошибка,
LoadState
является объектомLoadState.Error
.
Существует два способа использования LoadState
в пользовательском интерфейсе: использование прослушивателя или использование специального адаптера списка для представления состояния загрузки непосредственно в списке RecyclerView
.
Доступ к состоянию загрузки с помощью прослушивателя
Чтобы получить состояние загрузки для общего использования в вашем пользовательском интерфейсе, используйте поток loadStateFlow
или метод addLoadStateListener()
, предоставленный вашим PagingDataAdapter
. Эти механизмы обеспечивают доступ к объекту CombinedLoadStates
, который включает информацию о поведении LoadState
для каждого типа нагрузки.
В следующем примере PagingDataAdapter
отображает различные компоненты пользовательского интерфейса в зависимости от текущего состояния загрузки обновления:
Котлин
// 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 } }
Ява
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); });
Ява
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); });
Дополнительные сведения о CombinedLoadStates
см. в разделе Доступ к дополнительной информации о состоянии загрузки .
Представление состояния загрузки с помощью адаптера
Библиотека подкачки предоставляет еще один адаптер списка, называемый LoadStateAdapter
, с целью представления состояния загрузки непосредственно в отображаемом списке выгружаемых данных. Этот адаптер обеспечивает доступ к текущему состоянию загрузки списка, который вы можете передать пользовательскому держателю представления, отображающему информацию.
Сначала создайте класс держателя представления, который будет хранить ссылки на представления загрузки и ошибок на вашем экране. Создайте функцию bind()
, которая принимает LoadState
в качестве параметра. Эта функция должна переключать видимость представления на основе параметра состояния загрузки:
Котлин
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 } }
Ява
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); } }
Ява
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); } }
Затем создайте класс, реализующий LoadStateAdapter
, и определите методы onCreateViewHolder()
и onBindViewHolder()
. Эти методы создают экземпляр вашего пользовательского держателя представления и привязывают соответствующее состояние загрузки.
Котлин
// 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) }
Ява
// 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); } }
Ява
// 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); } }
Отображение состояния загрузки в виде верхнего или нижнего колонтитула.
Чтобы отобразить ход загрузки в верхнем и нижнем колонтитулах, вызовите метод withLoadStateHeaderAndFooter()
из объекта PagingDataAdapter
:
Котлин
pagingAdapter .withLoadStateHeaderAndFooter( header = ExampleLoadStateAdapter(adapter::retry), footer = ExampleLoadStateAdapter(adapter::retry) )
Ява
pagingAdapter .withLoadStateHeaderAndFooter( new ExampleLoadStateAdapter(pagingAdapter::retry), new ExampleLoadStateAdapter(pagingAdapter::retry));
Ява
pagingAdapter .withLoadStateHeaderAndFooter( new ExampleLoadStateAdapter(pagingAdapter::retry), new ExampleLoadStateAdapter(pagingAdapter::retry));
Вместо этого вы можете вызвать withLoadStateHeader()
или withLoadStateFooter()
если хотите, чтобы список RecyclerView
отображал состояние загрузки только в заголовке или только в нижнем колонтитуле.
Доступ к дополнительной информации о состоянии загрузки
Объект CombinedLoadStates
из PagingDataAdapter
предоставляет информацию о состояниях загрузки для вашей реализации PagingSource
, а также для вашей реализации RemoteMediator
, если таковая существует.
Для удобства вы можете использовать свойства refresh
, append
и prepend
из CombinedLoadStates
для доступа к объекту LoadState
для соответствующего типа загрузки. Эти свойства обычно подчиняются состоянию загрузки реализации RemoteMediator
, если таковое существует; в противном случае они содержат соответствующее состояние загрузки из реализации PagingSource
. Более подробную информацию о базовой логике см. в справочной документации по CombinedLoadStates
.
Котлин
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 } }
Ява
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; });
Ява
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; });
Однако важно помнить, что только состояния загрузки PagingSource
гарантированно синхронизируются с обновлениями пользовательского интерфейса. Поскольку свойства refresh
, append
и prepend
потенциально могут принимать состояние загрузки из PagingSource
или RemoteMediator
, их синхронность с обновлениями пользовательского интерфейса не гарантируется. Это может вызвать проблемы с пользовательским интерфейсом, когда загрузка завершается до того, как в пользовательский интерфейс будут добавлены какие-либо новые данные.
По этой причине удобные средства доступа хорошо подходят для отображения состояния загрузки в верхнем или нижнем колонтитуле, но в других случаях вам может потребоваться специальный доступ к состоянию загрузки из PagingSource
или RemoteMediator
. Для этой цели CombinedLoadStates
предоставляет свойства source
и mediator
. Каждое из этих свойств предоставляет объект LoadStates
, содержащий объекты LoadState
для PagingSource
или RemoteMediator
соответственно:
Котлин
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 } }
Ява
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; });
Ява
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; });
Операторы цепочки на LoadState
Поскольку объект CombinedLoadStates
предоставляет доступ ко всем изменениям состояния загрузки, важно фильтровать поток состояния загрузки на основе определенных событий. Это гарантирует, что вы обновите свой пользовательский интерфейс в подходящее время, чтобы избежать зависаний и ненужных обновлений пользовательского интерфейса.
Например, предположим, что вы хотите отобразить пустое представление, но только после завершения начальной загрузки данных. В этом варианте использования необходимо убедиться, что загрузка обновления данных началась, а затем дождаться состояния NotLoading
, чтобы подтвердить завершение обновления. Вы должны отфильтровать все сигналы, кроме тех, которые вам нужны:
Котлин
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) } }
Ява
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); });
Ява
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); } });
В этом примере ожидается обновление состояния загрузки обновления, но он срабатывает только тогда, когда состояние NotLoading
. Это гарантирует, что удаленное обновление полностью завершится до того, как произойдет какое-либо обновление пользовательского интерфейса.
API-интерфейсы Stream делают этот тип операций возможным. Ваше приложение может указать необходимые ему события загрузки и обрабатывать новые данные при выполнении соответствующих критериев.
{% дословно %}Рекомендуется для вас
- Примечание. Текст ссылки отображается, когда JavaScript отключен.
- Загрузка и отображение постраничных данных
- Страница из сети и базы данных
- Обзор библиотеки подкачки