A biblioteca Paging monitora o estado das solicitações de carregamento dos dados paginados e os expõe
usando a classe LoadState
.
O app pode registrar um listener usando o
PagingDataAdapter
para
receber informações sobre o estado atual e atualizar a IU da forma adequada. Esses
estados são fornecidos pelo adaptador porque são síncronos com a IU.
Isso significa que o listener recebe atualizações quando o carregamento da página é
aplicado à IU.
Um sinal LoadState
separado é fornecido para cada
LoadType
e cada tipo de fonte de dados
(PagingSource
ou
RemoteMediator
). O objeto
CombinedLoadStates
fornecido pelo listener fornece informações sobre o estado de carregamento
de todos esses sinais. Use essas informações detalhadas para exibir os
indicadores de carregamento adequados aos usuários.
Estados de carregamento
A biblioteca Paging usa o objeto
LoadState
para expor o estado de carregamento que será usado na IU. Os objetos LoadState
podem assumir uma de três formas, dependendo do
estado de carregamento atual:
- Se não houver operação de carregamento ativa nem erros,
LoadState
será um objetoLoadState.NotLoading
. Essa subclasse também inclui a propriedadeendOfPaginationReached
, que indica se o fim da paginação foi atingido. - Se houver uma operação de carregamento ativa,
LoadState
será um objetoLoadState.Loading
. - Se houver algum erro,
LoadState
será um objetoLoadState.Error
.
Há duas maneiras de usar LoadState
na IU: usar um listener ou um
adaptador de lista especial para apresentar o estado de carregamento diretamente na
lista
RecyclerView
.
Acessar o estado de carregamento usando um listener
Para acessar o estado de carregamento para uso geral na IU, use o fluxo
loadStateFlow
ou o método
addLoadStateListener()
fornecido pelo PagingDataAdapter
. Esses mecanismos fornecem acesso a
um objeto CombinedLoadStates
que inclui informações sobre o comportamento de LoadState
para cada tipo de carregamento.
No exemplo a seguir, o PagingDataAdapter
exibe componentes de IU
diferentes, dependendo do estado atual do carregamento de atualização:
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); });
Para mais informações sobre CombinedLoadStates
, consulte Acessar outras informações
sobre o estado de carregamento.
Exibir o estado de carregamento usando um adaptador
A biblioteca Paging oferece outro adaptador de lista chamado
LoadStateAdapter
para
apresentar o estado de carregamento diretamente na lista exibida de
dados paginados. Esse adaptador fornece acesso ao estado de carregamento atual da lista, que
pode ser transmitido para um armazenador de visualização personalizado que exibe as informações.
Primeiro, crie uma classe para armazenar visualizações que mantém referências às visualizações de carregamento e
de erro na tela. Crie uma função bind()
que aceite um LoadState
como
parâmetro. Essa função precisa alternar a visibilidade da visualização com base no
parâmetro do estado de carregamento:
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); } }
Em seguida, crie uma classe que implemente LoadStateAdapter
e defina os métodos
onCreateViewHolder()
e
onBindViewHolder()
.
Esses métodos criam uma instância do armazenador de visualização personalizado e vinculam
o estado de carregamento associado.
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); } }
Exibir o estado de carregamento como um cabeçalho ou rodapé
Para exibir o progresso do carregamento em um cabeçalho e rodapé, chame o método
withLoadStateHeaderAndFooter()
do objeto 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));
Em vez disso, você pode chamar
withLoadStateHeader()
ou
withLoadStateFooter()
se quiser que a lista RecyclerView
exiba o estado de carregamento apenas no
cabeçalho ou no rodapé.
Acessar informações adicionais sobre o estado de carregamento
O objeto CombinedLoadStates
do PagingDataAdapter
fornece informações sobre
os estados de carregamento para a implementação de PagingSource
e também para a
implementação de RemoteMediator
, se houver.
Por conveniência, é possível usar as propriedades
refresh
,
append
e
prepend
de CombinedLoadStates
para acessar um objeto LoadState
do
tipo de carregamento específico. Geralmente, essas propriedades são vinculadas ao estado de carregamento da
implementação de RemoteMediator
, se houver. Caso contrário, eles
contêm o estado de carregamento da implementação PagingSource
. Para ver informações mais detalhadas
sobre essa lógica, consulte a documentação de referência para
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; });
No entanto, é importante lembrar que apenas os estados de carregamento PagingSource
têm a garantia de serem síncronos com atualizações da IU. Como as propriedades refresh
,
append
e prepend
podem potencialmente receber o estado de carregamento de
PagingSource
ou RemoteMediator
, não há garantia de que elas sejam
síncronas com atualizações da interface. Isso pode causar problemas de IU em que o carregamento parece
terminar antes de qualquer dado novo ser adicionado a ela.
Por esse motivo, os acessadores de conveniência funcionam bem para exibir o estado de
carregamento em um cabeçalho ou rodapé, mas, para outros casos de uso, pode ser necessário
acessar especificamente o estado de carregamento de PagingSource
ou
RemoteMediator
. CombinedLoadStates
fornece as propriedades
source
e
mediator
para essa finalidade. Cada uma dessas propriedades expõe um objeto
LoadStates
que
contém os objetos LoadState
de PagingSource
ou RemoteMediator
,
respectivamente:
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; });
Operadores de cadeia em LoadState
Como o objeto CombinedLoadStates
fornece acesso a todas as mudanças no
estado de carregamento, é importante filtrar o fluxo do estado de carregamento com base em eventos
específicos. Isso garante que você atualize a IU no momento apropriado para evitar a
renderização lenta e atualizações desnecessárias.
Por exemplo, suponha que você queira exibir uma visualização vazia, mas somente depois que o
carregamento inicial de dados for concluído. Neste caso de uso, é necessário verificar se um
carregamento de atualização de dados foi iniciado e aguardar o estado NotLoading
confirmar
a conclusão. Filtre todos os sinais, exceto os
necessários:
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); } });
Este exemplo aguarda até que o estado de carregamento de atualização seja atualizado, mas só é acionado
quando o estado é NotLoading
. Isso garante que a atualização remota seja totalmente
concluída antes que qualquer atualização de IU aconteça.
As APIs de fluxo tornam esse tipo de operação possível. O app pode especificar os eventos de carregamento necessários e processar os novos dados quando os critérios apropriados forem atendidos.
Recomendados para você
- Observação: o texto do link aparece quando o JavaScript está desativado.
- Carregar e exibir dados paginados
- Página da rede e do banco de dados
- Visão geral da biblioteca Paging