Концепции и реализация Jetpack Compose
Библиотека Paging отслеживает состояние запросов на загрузку постраничных данных и предоставляет его через класс LoadState . Ваше приложение может зарегистрировать слушатель в PagingDataAdapter , чтобы получать информацию о текущем состоянии и соответствующим образом обновлять пользовательский интерфейс. Эти состояния предоставляются адаптером, поскольку они синхронны с пользовательским интерфейсом. Это означает, что ваш слушатель получает обновления, когда страница загружена в пользовательский интерфейс.
Для каждого LoadType и типа источника данных ( PagingSource или RemoteMediator ) предоставляется отдельный сигнал LoadState . Объект CombinedLoadStates , предоставляемый слушателем, содержит информацию о состоянии загрузки из всех этих сигналов. Вы можете использовать эту подробную информацию для отображения соответствующих индикаторов загрузки вашим пользователям.
Состояния загрузки
Библиотека Paging предоставляет доступ к состоянию загрузки для использования в пользовательском интерфейсе через объект 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 } }
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); });
Для получения дополнительной информации о CombinedLoadStates см. раздел «Доступ к дополнительной информации о состоянии загрузки» .
Представьте состояние загрузки с помощью адаптера.
Библиотека Paging предоставляет еще один адаптер для списков, называемый LoadStateAdapter предназначенный для отображения состояния загрузки непосредственно в отображаемом списке постраничных данных. Этот адаптер предоставляет доступ к текущему состоянию загрузки списка, который можно передать пользовательскому представлению-оболочке, отображающему эту информацию.
Сначала создайте класс-хранилище представления, который будет хранить ссылки на представления загрузки и ошибок на вашем экране. Создайте функцию bind() , которая принимает в качестве параметра ` LoadState . Эта функция должна переключать видимость представления в зависимости от параметра `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 } }
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); } }
Далее создайте класс, реализующий интерфейс LoadStateAdapter , и определите методы onCreateViewHolder() и onBindViewHolder() . Эти методы создают экземпляр вашего пользовательского ViewHolder и привязывают соответствующее состояние загрузки.
Котлин
// 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); } }
Отобразить состояние загрузки в виде заголовка или нижнего колонтитула.
Чтобы отобразить ход загрузки в заголовке и нижнем колонтитуле, вызовите метод withLoadStateHeaderAndFooter() из объекта PagingDataAdapter :
Котлин
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));
Вместо этого вы можете вызвать 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 } }
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; });
Однако важно помнить, что только состояния загрузки 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 } }
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; });
Операторы цепочки в 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) } }
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); } });
В этом примере ожидается обновление состояния загрузки, но запуск происходит только тогда, когда состояние становится NotLoading . Это гарантирует, что удалённое обновление полностью завершится до того, как произойдут какие-либо обновления пользовательского интерфейса.
API потоковой обработки данных позволяют выполнять подобные операции. Ваше приложение может указать необходимые события загрузки и обрабатывать новые данные, когда будут выполнены соответствующие критерии.
{% verbatim %}Рекомендуем вам
- Примечание: текст ссылки отображается, когда JavaScript отключен.
- Загрузка и отображение постраничных данных
- Страница из сети и базы данных
- Обзор библиотеки пейджинга