0Pricing
Android Academy · Aula

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 token String.
  • 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 - 1

Implementando 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.articles

Verificaçã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> implementa load() e getRefreshKey()
  • load() retorna LoadResult.Page com prevKey/nextKey ou LoadResult.Error
  • Uma chave null interrompe a paginação nessa direção
  • Pager(config, factory).flow produz um Flow<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

  1. Por que usar paginação
  2. PagingSource e Pager
  3. Paginação em listas do Compose
  4. RemoteMediator e cache
← Voltar para Android Academy