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 tokenString. - 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 - 1Implementar 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.articlesComprobació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>implementaload()ygetRefreshKey()load()devuelveLoadResult.PageconprevKey/nextKey, oLoadResult.Error- Una clave
nulldetiene la paginación en esa dirección Pager(config, factory).flowproduce unFlow<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
- Por qué usar Paging
- PagingSource y Pager
- Paging en listas de Compose
- RemoteMediator y caché