Acceda a la misma página con Paginación 3

publicado por Florina Muntenescu Advocate Developer Android

  Gráficos de 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á utilizando Flow LiveData o RxJava Fluible o Observable .
  • 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

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, implemente PagingSource 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 un PagingSource 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

  Una lista filtrada

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

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.



Compruebe también

en vivo desde Droidcon, incluida la mayor actualización de Gemini en Android Studio y más lanzamientos del SDK de Android.

Acabamos de lanzar nuestro episodio de otoño de #TheAndroidShow en YouTube etcétera desarrollador.android.comy esta vez …

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *