A biblioteca Paging fornece recursos avançados para carregar e exibir dados paginados de um conjunto de dados maior. Este guia demonstra como usar a biblioteca Paging para configurar um fluxo de dados paginados de uma fonte de dados de rede e exibi-los em uma lista lazy.
Definir uma fonte de dados
A primeira etapa é definir uma implementação da PagingSource para identificar a fonte de dados. A classe de API PagingSource inclui o método load,
que é substituído para indicar como extrair dados paginados da
fonte de dados correspondente.
Use a classe PagingSource diretamente para usar corrotinas do Kotlin para
carregamento assíncrono.
Selecionar tipos de chave e valor
PagingSource<Key, Value> tem dois parâmetros de tipo: Key e Value. A chave
define o identificador usado para carregar os dados, e o valor é o tipo dos
próprios dados. Por exemplo, se você carregar páginas de objetos User da rede
transmitindo números de página Int para Retrofit, selecione Int como o tipo Key
e User como o tipo Value.
Definir a PagingSource
O exemplo a seguir implementa um PagingSource que carrega páginas de itens
por número de página. O tipo Key é Int, e o tipo Value é User.
class ExamplePagingSource(
val backend: ExampleBackendService,
val query: String
) : PagingSource<Int, User>() {
override suspend fun load(
params: LoadParams<Int>
): LoadResult<Int, User> {
init {
// the data source is expected to be immutable
// invalidate PagingSource if data source
// has updated
backEnd.addDatabaseOnChangedListener {
invalidate()
}
}
try {
// Start refresh at page 1 if undefined.
val nextPageNumber = params.key ?: 1
val response = backend.searchUsers(query, nextPageNumber)
return LoadResult.Page(
data = response.users,
prevKey = null, // Only paging forward.
nextKey = nextPageNumber + 1
)
} catch (e: Exception) {
// Handle errors in this block and return LoadResult.Error for
// expected errors (such as a network failure).
}
}
override fun getRefreshKey(state: PagingState<Int, User>): Int? {
// Try to find the page key of the closest page to anchorPosition from
// either the prevKey or the nextKey; you need to handle nullability
// here.
// * prevKey == null -> anchorPage is the first page.
// * nextKey == null -> anchorPage is the last page.
// * both prevKey and nextKey are null -> anchorPage is the
// initial page, so return null.
return state.anchorPosition?.let { anchorPosition ->
val anchorPage = state.closestPageToPosition(anchorPosition)
anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
}
}
}
Uma implementação típica de PagingSource transmite parâmetros fornecidos no
construtor ao método load para carregar os dados apropriados em uma consulta. No
exemplo acima, esses parâmetros são:
backend: uma instância do serviço de back-end que fornece os dadosquery: a consulta de pesquisa a ser enviada ao serviço indicado porbackend
O objeto LoadParams
contém informações sobre a operação de carregamento a ser executada. Isso
inclui a chave e o número de itens a serem carregados.
O objeto LoadResult
contém o resultado da operação de carregamento. LoadResult é uma classe selada
que pode assumir uma de três formas, dependendo do êxito da chamada load:
- Se o carregamento for bem-sucedido, um objeto
LoadResult.Pageé retornado. - Se o carregamento não for bem-sucedido, um objeto
LoadResult.Erroré retornado. - Se o
PagingSourcenão for mais válido e precisar ser substituído por uma nova instância (por exemplo, devido a uma mudança nos dados subjacentes), retorne um objetoLoadResult.Invalid.
A figura abaixo ilustra como a função load neste exemplo recebe a chave
para cada carregamento e para o carregamento seguinte.
load usa e atualiza a chave.
A implementação de PagingSource também precisa aplicar um método
getRefreshKey
que receba um objeto
PagingState como um
parâmetro. Ele retorna a chave para transmitir ao método load quando os dados são
atualizados ou invalidados após o carregamento inicial. A biblioteca Paging chama esse
método automaticamente nas próximas atualizações dos dados.
Solucionar erros
As solicitações para carregar dados podem falhar por diversos motivos, especialmente no carregamento
pela rede. Informe erros encontrados durante o carregamento retornando um
objeto LoadResult.Error do método load.
Por exemplo, é possível identificar e relatar erros de carregamento em ExamplePagingSource
do exemplo anterior adicionando o seguinte ao método load:
catch (e: IOException) {
// IOException for network failures.
return LoadResult.Error(e)
} catch (e: HttpException) {
// HttpException for any non-2xx HTTP status codes.
return LoadResult.Error(e)
}
Para mais informações sobre como lidar com erros de Retrofit, consulte as amostras na
referência da API PagingSource.
PagingSource coleta e envia objetos LoadResult.Error à interface para
que você possa tomar medidas quanto a eles. Para mais informações sobre como expor o estado de carregamento
na interface, consulte Gerenciar e apresentar estados de
carregamento.
Configurar um fluxo de PagingData
Em seguida, você precisa de um fluxo de dados paginados da implementação de PagingSource.
Configure o fluxo de dados no seu ViewModel. A classe Pager oferece
métodos que expõem um fluxo reativo de objetos PagingData de uma
PagingSource. A biblioteca Paging expõe o fluxo de dados como um Flow.
Ao criar uma instância de Pager para configurar seu fluxo reativo, é necessário
fornecer à instância um objeto de configuração PagingConfig e uma
função que informe Pager como ter uma instância da implementação de PagingSource, conforme mostrado no exemplo a seguir.
class UserViewModel(
private val backend: ExampleBackendService,
private val query: String
) : ViewModel() {
val userPagingFlow: Flow<PagingData<User>> = Pager(
// Configure how data is loaded by passing additional properties to
// PagingConfig, such as pageSize and enabling or disabling placeholders.
config = PagingConfig(
pageSize = 20,
enablePlaceholders = true
),
pagingSourceFactory = {
ExamplePagingSource(backend, query)
}
)
.flow
.cachedIn(viewModelScope)
}
O operador cachedIn torna o fluxo de dados compartilhável e armazena em cache os dados carregados
com o CoroutineScope fornecido. Sem cachedIn, não é possível coletar o PagingData. Esse exemplo usa o viewModelScope fornecido pelo
artefato lifecycle-viewmodel-ktx do ciclo de vida.
O objeto Pager chama o método load do objeto PagingSource,
fornecendo o objeto LoadParams e recebendo o
objeto LoadResult em troca.
Coletar e mostrar os dados na interface
Para conectar o stream paginado à interface, extraia o fluxo do seu ViewModel e
transmita-o ao elemento combinável da lista.
@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
val userFlow = viewModel.userPagingFlow
UserList(flow = userFlow)
}
Use collectAsLazyPagingItems para converter o fluxo PagingData em
LazyPagingItems. Em seguida, use a API items em um LazyColumn para criar o layout
de cada item.
Forneça um identificador exclusivo e estável para cada item usando itemKey.
O exemplo a seguir usa it.id (referenciando a propriedade User.id) porque
ele permanece estável para a instância User em todas as atualizações de dados.
@Composable
fun UserList(flow: Flow<PagingData<User>>) {
val lazyPagingItems = flow.collectAsLazyPagingItems()
LazyColumn {
items(
lazyPagingItems.itemCount,
key = lazyPagingItems.itemKey { it.id }
) { index ->
val user = lazyPagingItems[index]
if (user != null) {
UserRow(user)
} else {
UserPlaceholder()
}
}
}
}
A biblioteca Paging usa null para marcadores de posição enquanto uma página está sendo carregada. Portanto, se você ativou os marcadores, precisa processar os valores null no bloco de conteúdo.
Agora a lista mostra os dados paginados, e a biblioteca Paging carrega outras páginas conforme o usuário rola a tela.
Outros recursos
Para saber mais sobre a biblioteca Paging, consulte os seguintes recursos extras:
Documentação
Visualiza conteúdo
Recomendados para você
- Observação: o texto do link aparece quando o JavaScript está desativado
- Página da rede e do banco de dados
- Migrar para a Paging 3
- Visão geral da biblioteca Paging