publicado por Florina Muntenescu Advocate Developer Android
La biblioteca Paging le permite cargar grandes cantidades de datos de forma gradual y elegante, reduciendo el uso de la red y los recursos del sistema. Nos dijo que la API Paging 2.0 no era suficiente: deseaba un manejo de errores más fácil, más flexibilidad para implementar transformaciones de lista como mapa o filtro
y soporte para separadores de lista, encabezado y pie de página. Así que lanzamos Paging 3.0 (ahora en alpha02), una reescritura completa de la biblioteca utilizando las rutinas de Kotlin (que aún admiten usuarios de Java) y que ofrecen la funcionalidad requerida.
Aspectos de Paging 3
La API de Paging 3 brinda soporte para características comunes que de otro modo debería implementar al cargar datos en las páginas:
- Realiza un seguimiento de las teclas que se utilizarán para recuperar la página siguiente y anterior.
- Solicita automáticamente la siguiente página corregida cuando el usuario se desplaza hasta el final de los datos cargados.
- Garantiza que no se activen múltiples solicitudes simultáneamente.
- Realiza un seguimiento del estado de carga y le permite verlo en un elemento de la lista
RecyclerView
o en cualquier otro lugar de la interfaz de usuario y ofrece una funcionalidad de reintento fácil para cargas fallidas . - Habilite operaciones comunes como mapa o
filtro
en la lista que se mostrará, independientemente de si está utilizandoFlow
LiveData
o RxJavaFluible
oObservable
. - Proporciona una manera fácil de implementar separadores de lista.
- Simplifica el almacenamiento en caché de datos, asegurándose de no realizar transformaciones de datos con cada cambio de configuración.
También hemos hecho muchos componentes de Paging 3 compatibles con Paging 2.0; así que si ya usa paginación en su aplicación, puede migrar de forma incremental.
Adoptando Paging 3 en su aplicación
Supongamos que estamos implementando una aplicación que muestra todos los buenos doggos. Obtenemos los doggos de una API GoodDoggos
que admite la paginación basada en índices. Examinemos los componentes de Paginación que necesitamos implementar y cómo se adaptan a la arquitectura de su aplicación. Los siguientes ejemplos estarán en Kotlin, utilizando corutinas. Para ver ejemplos en el lenguaje de programación Java que utiliza LiveData / RxJava, consulte la documentación.
La biblioteca de paginación se integra directamente en la arquitectura de la aplicación de Android recomendada en cada nivel de la aplicación:
Componentes de paginación y su integración en la arquitectura de la aplicación "
id =" imgCaption ">
Definición del origen de datos
Dependiendo de la ubicación desde la que está cargando los datos, implemente solo PagingSource
o PagingSource
y un RemoteMediator
:
- Si está cargando datos de a una sola fuente como una red, una base de datos local, un archivo, etc., implemente
PagingSource
(si está usando Room, implementePagingSource
para ti a partir de la habitación 2.3.0-alfa). - Si está cargando datos de una fuente en capas como fuente de datos de red con un caché de base de datos local, implemente
RemoteMediator
para combinar las dos fuentes y unPagingSource
para el caché de la base de datos local.
PagingSource
A PagingSource
define la fuente de datos de paginación y cómo recuperar datos de esa única fuente. PagingSource
debería ser parte del nivel de repositorio. Implemente load ()
para recuperar datos paginados de la fuente de datos y devolver los datos cargados junto con información sobre las claves siguiente y anterior. Esta es una función de suspensión
para que pueda llamar a otras funciones de suspensión
como la llamada de red aquí:
Clase DoggosRemotePagingSource ( val backend: GoodDoggosService ): PagingSource() { ignorar la divertida carga colgante ( params: LoadParams ): LoadResult { tratar { // Carga la página 1 si no está definida. val nextPageNumber = params.key?: 1 respuesta val = backend.getDoggos (nextPageNumber) return LoadResult.Page ( data = response.doggos, prevKey = null, // Solo paginación hacia adelante. nextKey = response.nextPageNumber + 1 ) } catch (e: Exception) { // Manejar errores en este bloque return LoadResult.Error (excepción) } } }
PagingData y Pager
El contenedor de datos paginados se llama PagingData
. Cada vez que se actualizan los datos, se crea una nueva instancia de PagingData
. Para crear un flujo de PagingData
cree una instancia Pager
utilizando un objeto de configuración PagingConfig
y una función que le dice al Pager
cómo obtener un & Instancia de su implementación PagingSource
.
En su ViewModel
construya el objeto Pager
y exponga una secuencia
a la interfaz de usuario. Flow
tiene un método práctico cachedIn ()
que hace que el flujo de datos se pueda compartir y permite almacenar en caché el contenido de un Flow
en un CoroutineScope
. De esta forma, si las transformaciones se implementan en la secuencia de datos, no se activarán nuevamente cada vez que se recopile la secuencia
después de la recreación Actividad
. El almacenamiento en caché debe realizarse lo más cerca posible del nivel de la interfaz de usuario, pero no del nivel de la interfaz de usuario, ya que queremos asegurarnos de que persista más allá de cambiar la configuración. El mejor lugar para esto sería en un ViewModel
usando el viewModelScope
:
val doggosPagingFlow = Pager (PagingConfig (pageSize = 10)) { DogRemotePagingSource (goodDoggosService) } .flow.cachedIn (viewModelScope)
PagingDataAdapter
Para conectar un RecyclerView
a PagingData
implementar un PagingDataAdapter
:
Clase DogAdapter
(diffCallback: DiffUtil.ItemCallback): PagingDataAdapter (diffCallback) { anular la diversión onCreateViewHolder ( padre: ViewGroup, viewType: Int ): DogViewHolder { volver DogViewHolder (padre) } anular la diversión enBindViewHolder (propietario: DogViewHolder, ubicación: Int) { elemento val = getItem (posición) if (item == null) { holder.bindPlaceholder () } otro { titular.bind (entrada) } } }
Entonces, en su negocio
/ Fragment
deberá recoger el flujo
y enviarlo a PagingDataAdapter
. Así es como se vería la implementación en una actividad onCreate ()
:
val viewModelo por viewModels() val pagingAdapter = DogAdapter (DogComparator) val recyclerView = findViewById (R.id.recycler_view) recyclerView.adapter = pagingAdapter lifecycleScope.launch { viewModel.doggosPagingFlow.collectLatest {pagingData -> pagingAdapter.submitData (pagingData) } }
Transformaciones de datos paginados
Mostrando una lista filtrada
id = "imgCaption">
La transformación de flujos PagingData
es muy similar a la forma en que se haría con cualquier otro flujo de datos. Por ejemplo, si solo queremos mostrar doggos juguetones de nuestro Flow <PagingData
debemos mapear el objeto Flow
y luego el filtro
PagingData
:
doggosPagingFlow.map {pagingData -> pagingData.filter {dog -> dog.isPlayful} }
lista con separadores
id = "imgCaption">
La adición de separadores de listas también es una transformación de datos paginados en los que transformamos PagingData
para insertar objetos separadores en la lista. Por ejemplo, podemos insertar separadores de letras para los nombres de nuestros doggos. Cuando utilice separadores, deberá implementar su propia clase de modelo de interfaz de usuario que admita los nuevos elementos separadores. Para modificar PagingData
para agregar separadores, utilizará la transformación insertSeparators
:
pager.flow.map {pagingData: PagingData-> pagingData.map {doggo -> // Convierte elementos de transmisión a UiModel.DogModel. UiModel.DogModel (Doggo) } .insertSeparators {before: Dog, after: Dog -> devuelve if (después de == nulo) { // estamos al final de la lista nulo } if (before == null || before.breed! = after.breed) { // juega arriba y abajo varios, muestra el separador UiModel.SeparatorItem (after.breed) } otro { // sin separador nulo } } } } .cachedIn (viewModelScope)
Al igual que antes, estamos usando cachedIn
justo antes del nivel de la interfaz de usuario. Esto garantiza que los datos cargados y los resultados de cualquier transformación puedan almacenarse en caché y reutilizarse después de un cambio de configuración.
Trabajo de paginación avanzado con RemoteMediator
Si está paginando datos de una fuente en capas necesita implementar un RemoteMediator
. Por ejemplo, en la implementación de esta clase es necesario solicitar datos de la red y guardarlos en la base de datos. El método load ()
se activará siempre que no haya más datos en la base de datos para mostrar. Según el PagingState
y el LoadType
podemos generar la solicitud en la página siguiente.
Es su responsabilidad definir cómo se compilan y almacenan las claves de la página remota anterior y siguiente, ya que la biblioteca de paginación no sabe cómo se ve la API. Por ejemplo, puede asociar claves remotas con todos los elementos recibidos de la red y guardarlos en la base de datos.
ignore la suspensión de carga divertida (loadType: LoadType, estado: PagingState): MediatorResult { val page = ... // calculado en base a loadType y estado tratar { val doggos = backend.getDoggos (página) doggosDatabase.doggosDao (). insertAll (doggos) val endOfPaginationReached = emails.isEmpty () return MediatorResult.Success (endOfPaginationReached = endOfPaginationReached) } captura (excepción: excepción) { return MediatorResult.Error (excepción) } }
Cuando carga datos de la red y los almacena en la base de datos, la base de datos es la fuente de la verdad para los datos que se muestran en la pantalla. Esto significa que la interfaz de usuario mostrará datos de su base de datos, por lo que deberá implementar un PagingSource
para su base de datos. Si está utilizando Room, solo necesitará agregar una nueva consulta a su DAO que devuelva un PagingSource
:
@Query ("SELECCIONAR * DE doggos") diversión getDoggos (): PagingSource
La implementación Pager
cambia ligeramente en este caso, ya que la instancia RemoteMediator
también debe pasarse:
val pagingSourceFactory = {database.doggosDao (). GetDoggos ()} volver Pager ( config = PagingConfig (pageSize = NETWORK_PAGE_SIZE), remoteMediator = DoggosRemoteMediator (servicio, base de datos), pagingSourceFactory = pagingSourceFactory ) .flow
Consulte los documentos para obtener más información sobre el uso de RemoteMediator. Para una implementación completa de RemoteMediator
en una aplicación, consulte el paso 15 del código de paginación y el código que lo acompaña.
Diseñamos la biblioteca Paging 3 para ayudarlo a cumplir con los usos simples y complejos de Paging. Simplifique su trabajo con grandes conjuntos de datos, ya sea que se carguen desde la red, desde una base de datos, desde la memoria caché en memoria o desde una combinación de estas fuentes. La biblioteca está construida sobre corutinas y Flow
facilitando la recuperación de las funciones de suspensión y la operación con flujos de datos.
Dado que Paging 3 todavía está en la versión alfa, ¡necesitamos su ayuda para mejorarlo! Para comenzar, obtenga más información sobre la paginación en nuestra documentación y pruébelo tomando nuestro codelab o verificando el ejemplo. Entonces, háganos saber cómo podemos mejorar la biblioteca creando problemas en el Rastreador de problemas.