PagingSource e Pager
Defina como as páginas são carregadas.
PagingSource e Pager é uma aula grátis de Android Academy no CoddyKit. Esta é a aula 2 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de Android Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de Android Academy inclui 4 aulas no total.
Definindo como as páginas são carregadas
Para paginar dados de uma API de rede, você escreve uma PagingSource. Ela responde a duas perguntas para o Paging 3:
- Como carregar a página correspondente a uma determinada chave?
- Se o usuário atualizar, de onde devo continuar?
Em seguida, você a envolve em uma Pager que a transforma em um Flow<PagingData>.
Parâmetros de tipo de PagingSource
PagingSource<Key, Value> recebe dois parâmetros de tipo:
- Key - identifica uma página. Para uma API baseada em números de página, é
Int; para uma API baseada em cursor, é um tokenString. - Value - o tipo do item, por exemplo,
Article.
import androidx.paging.PagingSource
import androidx.paging.PagingState
class ArticlePagingSource(
private val api: ArticleApi
) : PagingSource<Int, Article>() {
// implement load() and getRefreshKey()
}Implementando load()
load() é uma função suspend. Ela recebe params.key (a página a ser buscada) e retorna um LoadResult.
Em caso de sucesso, você retorna LoadResult.Page com os itens e as chaves anterior e seguinte. O valor null para uma chave significa que não há página nessa direção.
override suspend fun load(
params: LoadParams<Int>
): LoadResult<Int, Article> {
val page = params.key ?: 1 // first load has a null key
return try {
val response = api.getArticles(page = page, size = params.loadSize)
LoadResult.Page(
data = response.items,
prevKey = if (page == 1) null else page - 1,
nextKey = if (response.items.isEmpty()) null else page + 1
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}Por que prevKey e nextKey são importantes
A paginação usa nextKey para carregar dados à frente conforme o usuário rola para baixo e prevKey para carregar dados para trás (útil ao começar no meio de uma lista).
Retornar null para nextKey informa à paginação que não há mais páginas, então ela para de fazer solicitações. Esquecer isso pode causar solicitações vazias infinitas.
// Stop forward paging when the server returns an empty page
nextKey = if (response.items.isEmpty()) null else page + 1
// Stop backward paging at the first page
prevKey = if (page == 1) null else page - 1Implementando getRefreshKey()
Quando a lista é atualizada (por gesto de puxar para atualizar ou por invalidação), a paginação precisa saber qual página recarregar para que o usuário permaneça aproximadamente no mesmo lugar.
getRefreshKey() usa a anchorPosition atual — o item mais próximo da área visível — para escolher uma chave adequada.
override fun getRefreshKey(state: PagingState<Int, Article>): Int? {
return state.anchorPosition?.let { anchor ->
val closestPage = state.closestPageToPosition(anchor)
closestPage?.prevKey?.plus(1)
?: closestPage?.nextKey?.minus(1)
}
}Tratando erros de forma adequada
Envolva a chamada de rede em um try/catch e retorne LoadResult.Error(e) em caso de falha. A paginação disponibiliza isso na interface como um LoadState.Error, permitindo exibir um botão de nova tentativa.
Nunca deixe uma exceção escapar de load(): capture-a e converta-a em LoadResult.Error.
return try {
val response = api.getArticles(page = page, size = params.loadSize)
LoadResult.Page(
data = response.items,
prevKey = if (page == 1) null else page - 1,
nextKey = if (response.items.isEmpty()) null else page + 1
)
} catch (e: IOException) { // no network
LoadResult.Error(e)
} catch (e: HttpException) { // non-2xx response
LoadResult.Error(e)
}Criando um Pager
Um Pager conecta seu PagingConfig a uma fábrica que cria uma nova PagingSource. Sua propriedade .flow é um Flow<PagingData> que a interface coleta.
A expressão lambda da fábrica deve criar uma fonte nova a cada vez, porque a paginação invalida e recria a fonte durante a atualização.
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import kotlinx.coroutines.flow.Flow
class ArticleRepository(private val api: ArticleApi) {
fun articleStream(): Flow<PagingData<Article>> = Pager(
config = PagingConfig(pageSize = 20, prefetchDistance = 5),
pagingSourceFactory = { ArticlePagingSource(api) }
).flow
}cachedIn para ViewModels
Coletar PagingData é uma operação de execução única; coletá-lo novamente reinicia o carregamento. Para sobreviver a mudanças de configuração e permitir que vários coletores compartilhem os dados, armazene o fluxo em cache no viewModelScope usando cachedIn.
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.cachedIn
class ArticleViewModel(
repo: ArticleRepository
) : ViewModel() {
val articles = repo.articleStream()
.cachedIn(viewModelScope)
}loadSize versus pageSize
Observe que load() lê params.loadSize, e não diretamente o seu pageSize configurado.
No primeiro carregamento, a paginação pode solicitar um bloco inicial maior (controlado por initialLoadSize em PagingConfig, cujo padrão é três vezes o tamanho da página). Sempre passe params.loadSize para sua API, para que a solicitação corresponda ao que a paginação espera receber.
// Correct: respect the size Paging asks for
val response = api.getArticles(page = page, size = params.loadSize)
// PagingConfig can tune the first load:
PagingConfig(pageSize = 20, initialLoadSize = 40)APIs baseadas em cursor
Nem toda API usa números de página. Algumas retornam um cursor ou token que aponta para a próxima página. O padrão é idêntico: basta alterar o tipo de Key para String e usar o token da resposta.
class CursorArticleSource(
private val api: ArticleApi
) : PagingSource<String, Article>() {
override suspend fun load(
params: LoadParams<String>
): LoadResult<String, Article> {
val cursor = params.key // null on first load
return try {
val res = api.getArticles(cursor = cursor, size = params.loadSize)
LoadResult.Page(
data = res.items,
prevKey = null, // forward-only cursor
nextKey = res.nextCursor // null when exhausted
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
override fun getRefreshKey(state: PagingState<String, Article>) = null
}Juntando tudo
Agora você tem a camada de dados completa: uma PagingSource que carrega uma página, um Pager que transmite páginas e um ViewModel que armazena o fluxo em cache.
A camada de interface simplesmente coleta viewModel.articles — que renderizaremos na próxima lição com uma LazyColumn do Compose.
// Data layer summary
// 1. ArticlePagingSource : PagingSource<Int, Article>
// 2. Pager(config, factory).flow -> Flow<PagingData<Article>>
// 3. ViewModel: repo.articleStream().cachedIn(viewModelScope)
// UI just collects viewModel.articlesVerificação rápida
Em uma PagingSource baseada em números de página, o que significa retornar nextKey = null de load()?
Recapitulação: PagingSource e Pager
Você criou a camada de dados para a paginação:
PagingSource<Key, Value>implementaload()egetRefreshKey()load()retornaLoadResult.PagecomprevKey/nextKeyouLoadResult.Error- Uma chave
nullinterrompe a paginação nessa direção Pager(config, factory).flowproduz umFlow<PagingData>cachedIn(viewModelScope)mantém os dados durante mudanças de configuração
Próximo: renderizar esse fluxo em uma lista do Compose.
Perguntas Frequentes
A aula “PagingSource e Pager” é grátis?
Sim — o texto completo de “PagingSource e Pager” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de Android Academy, atualize para CoddyKit PRO. O curso de Android Academy inclui 4 aulas no total.
O que vou aprender em “PagingSource e Pager”?
Defina como as páginas são carregadas. Você pratica Android Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.
Preciso ter experiência prévia para começar Android Academy?
Nenhuma experiência prévia é necessária. Android Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 2 de 4.
Quanto tempo leva a aula “PagingSource e Pager”?
A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.
Posso escrever e executar código nesta aula de Android Academy?
Sim. Cada aula de Android Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.
Todas as aulas deste curso
- Por que usar paginação
- PagingSource e Pager
- Paginação em listas do Compose
- RemoteMediator e cache