0Pricing
Android Academy · Lección

PagingSource y Pager

Defina cómo se cargan las páginas

PagingSource y Pager es una lección gratuita de Android Academy en CoddyKit. Esta es la lección 2 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Android Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Android Academy incluye 4 lecciones en total.

Definir cómo se cargan las páginas

Para paginar desde una API de red, debe escribir un PagingSource. Este responde a dos preguntas para Paging 3:

  • ¿Cómo se carga la página correspondiente a una clave determinada?
  • Si el usuario actualiza la lista, ¿desde dónde se debe reanudar la carga?

A continuación, debe envolverlo en un Pager que lo convierta en un Flow<PagingData>.

Parámetros de tipo de PagingSource

PagingSource<Key, Value> recibe dos parámetros de tipo:

  • Key - identifica una página. Para una API basada en números de página es Int; para una API basada en cursores es un token String.
  • Value - el tipo de elemento, por ejemplo Article.
import androidx.paging.PagingSource
import androidx.paging.PagingState

class ArticlePagingSource(
    private val api: ArticleApi
) : PagingSource<Int, Article>() {
    // implement load() and getRefreshKey()
}

Implementar load()

load() es una función suspend. Recibe params.key (la página que se debe obtener) y devuelve un LoadResult.

Si la operación tiene éxito, debe devolver LoadResult.Page con los elementos y las claves anterior y siguiente. El valor null para una clave indica que no existe ninguna página en esa dirección.

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 qué son importantes prevKey y nextKey

Paging utiliza nextKey para cargar páginas posteriores mientras el usuario se desplaza hacia abajo, y prevKey para cargar páginas anteriores (lo que resulta útil al comenzar en el centro de una lista).

Devolver null para nextKey indica a Paging que no hay más páginas y, por tanto, deja de hacer solicitudes. Si lo olvida, puede provocar solicitudes vacías 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

Implementar getRefreshKey()

Cuando se actualiza la lista (mediante el gesto de deslizar para actualizar o una invalidación), Paging necesita saber qué página debe volver a cargar para que el usuario permanezca aproximadamente en el mismo lugar.

getRefreshKey() utiliza la anchorPosition actual, es decir, el elemento más cercano al área visible, para elegir una clave adecuada.

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)
    }
}

Gestionar los errores correctamente

Incluya la llamada de red en un bloque try/catch y devuelva LoadResult.Error(e) si se produce un error. Paging muestra este estado como un LoadState.Error en la interfaz de usuario, de modo que puede mostrar un botón de reintento.

No permita nunca que una excepción escape de load(): captúrela y conviértala en 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)
}

Crear un Pager

Un Pager vincula su PagingConfig con una factoría que crea un PagingSource nuevo. Su propiedad .flow es un Flow<PagingData> que la interfaz de usuario recopila.

La lambda de la factoría debe crear un origen nuevo cada vez, porque Paging invalida y vuelve a crear el origen al actualizarse.

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

Recopilar PagingData es una operación de un solo uso; volver a recopilarlo reinicia la carga. Para conservar los datos durante los cambios de configuración y permitir que varios recopiladores los compartan, almacene en caché el flujo en viewModelScope mediante 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 frente a pageSize

Tenga en cuenta que load() lee params.loadSize, no directamente el pageSize configurado.

En la primera carga, Paging puede solicitar un bloque inicial más grande (controlado por initialLoadSize en PagingConfig, cuyo valor predeterminado es tres veces el tamaño de página). Pase siempre params.loadSize a su API para que la solicitud coincida con la cantidad que Paging espera recibir.

// 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 basadas en cursores

No todas las API utilizan números de página. Algunas devuelven un cursor o token que apunta a la página siguiente. El patrón es idéntico: solo debe cambiar el tipo de Key a String y utilizar el token de la respuesta.

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
}

Unir todas las piezas

Ahora dispone de la capa de datos completa: un PagingSource que carga una página, un Pager que transmite las páginas y un ViewModel que almacena el flujo en caché.

La capa de interfaz de usuario simplemente recopila viewModel.articles, que mostraremos en la próxima lección mediante un LazyColumn de 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

Comprobación rápida

En un PagingSource basado en números de página, ¿qué indica devolver nextKey = null desde load()?

Resumen: PagingSource y Pager

Ha creado la capa de datos para la paginación:

  • PagingSource<Key, Value> implementa load() y getRefreshKey()
  • load() devuelve LoadResult.Page con prevKey/nextKey, o LoadResult.Error
  • Una clave null detiene la paginación en esa dirección
  • Pager(config, factory).flow produce un Flow<PagingData>
  • cachedIn(viewModelScope) conserva los datos durante los cambios de configuración

Siguiente: mostrar este flujo en una lista de Compose.

Preguntas frecuentes

¿La lección «PagingSource y Pager» es gratis?

Sí — el texto completo de «PagingSource y Pager» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Android Academy, actualiza a CoddyKit PRO. El curso de Android Academy incluye 4 lecciones en total.

¿Qué aprenderé en «PagingSource y Pager»?

Defina cómo se cargan las páginas Practicas Android Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar Android Academy?

No se requiere experiencia previa. Android Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 2 de 4.

¿Cuánto tiempo toma la lección «PagingSource y Pager»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de Android Academy?

Sí. Cada lección de Android Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Por qué usar Paging
  2. PagingSource y Pager
  3. Paging en listas de Compose
  4. RemoteMediator y caché
← Volver a Android Academy