Generalidades

Jetpack Paging 3 para Android: comprensión de su historia y evolución mediante la creación de un componente de paginación universal | por AMARO | junio de 2021

Amaro

Autor: Levy Schiavetti – Desarrollador de Android en AMARO

1*4a010TFKLkgNOSBUSgdiJw

Skimming historia

aunque Azaraskin, Esto Scroll infinito Creadores, lamentan su gran contribución al mundo tecnológico, es difícil no admitir que hace que nuestra experiencia sea más fácil, especialmente más fluida.Desde entonces, los usuarios pueden ver cientos de publicaciones, productos o cualquier tipo de datos presentados en una lista sin hacer clic. próximo o ver más Botón y espere a que la página se actualice.

No dediqué mucho tiempoelectrónico La empresa vio rápidamente una gran oportunidad, por lo que los desarrolladores comenzaron a escribir código basado en esta idea y crear sus propios componentes de paginación.Cada componente tiene sus propias necesidades específicas, pero todos tienen el mismo propósito, a saber:

«Supervise el estado del componente de desplazamiento de la interfaz de usuario, realice un seguimiento de su número de página actual y otros datos para activar solicitudes de datos y gestionar la carga y otros indicadores»

Discurso Androide, Esto requiere que miremos Adaptador para RecyclerView Pasar una ubicación Oyente de desplazamiento La interfaz utilizada para activar una nueva API o solicitud de base de datos. Tendremos un montón de código para controlar solicitudes como esta:

override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) 
super.onScrolled(recyclerView, dx, dy)
val visibleItemCount = layoutManager.childCount
val totalItemCount = layoutManager.itemCount
val firstVisibleItemPosition =
layoutManager.findFirstVisibleItemPosition()
if (!isLoading() && !isLastPage())
if (visibleItemCount + firstVisibleItemPosition >= totalItemCount && firstVisibleItemPosition >= 0)
loadMoreItems()


A medida que comienzan a surgir otras funciones y requisitos, no es tan fácil de implementar y se vuelve más complicado.

En el código anterior, encontramos el siguiente método: la ultima pagina, cargando, Buscar FirstVisibleItemPosition, Cargar más artículos Y otros. Todos estos deben calcular si es necesario volver a llamar a la fuente de datos para obtener más datos. Pero es demasiado para las vistas de cálculo y procesamiento, porque solo puede mostrar datos, que es lo que debería ser. Tener tanta lógica en la vista es obviamente una desventaja.
Idealmente, al seguir algunas buenas prácticas para separar preocupaciones, no las queremos en nuestros proyectos. Por lo general, queremos que nuestros ViewModels o Repositories manejen estos, mientras que View solo muestra el contenido que se ha definido o calculado.

Biblioteca de paginación de Jetpack

Entre otras novedades interesantes, Google Anunció la primera versión en I / O en 2018 Biblioteca de paginación de JetpackA partir de ese momento, ya no es necesario que la vista procese la página actual o calcule cuándo se activa la solicitud. De hecho, no se trata solo de Vistas, porque la mayoría de los cálculos se reducen en todas partes, dejando la mayor parte de la responsabilidad a esta biblioteca especial.

En el momento de redactar este artículo, existen actualmente tres versiones de la biblioteca. Independientemente de la versión, sus principales componentes y responsabilidades son básicamente los mismos, que incluyen:

– Esto fuente de datos, responsable de administración Base de datos o red Afirmar.

– Esto adaptador,responsable de Presta atención a la tendencia Punto de vista estadoSolicite más datos del DataSource cuando sea necesario, todo está detrás de escena.

Pero desde que salió la primera versión, Biblioteca de paginación Los cambios son rápidos. Se han desaprobado algunos métodos y se ha cambiado el nombre de algunas clases y se ha mejorado el rendimiento.Ahora es compatible flujo con Datos en tiempo real.
Hasta ahora, esta es una mejora importante y ahora nos facilita la vida.

versión previa

Cuando se trata de implementación de nuestro lado, fuente de datos Es posible que se hayan producido cambios importantes. Su primera versión alguna vez contenía tres métodos, cuyo propósito era manejar la lógica del desplazamiento en diferentes momentos:

Cargar inicialización ()
Se activa cuando los datos se cargan por primera vez y es responsable de llevar el volumen de datos inicial a la vista.

Después de cargar()
Siempre que el usuario se desplaza hacia abajo y detecta que se necesitan más datos, este método es el método responsable de procesar la siguiente solicitud

Antes de cargar ()
De acuerdo a tu mecanismo de paginación, puedes optar por no almacenar la página después de cierto punto en el tiempo, por lo que si el usuario decide desplazarse hacia arriba, se activará este método, que se encarga de obtener datos anteriores que ya no están disponibles en el cache.

Esto, por supuesto, es mucho más limpio que nuestra propia gestión de vistas, adaptadores y repositorios, pero todavía se siente como una implementación compleja y larga, porque podemos ver:

override fun loadInitial(
param: LoadInitialParams<Long>, callback:
LoadInitialCallback<Long, Article>
)
/*
1. Handle Loading or whatever necessary before the request
2. Perform initial request of data
3. Handle success or error result
4. Handle Loading or whatever necessary after the request
*/
override fun loadBefore(
param: LoadParams<Long>, callback:
LoadCallback<Long, Article>
)
/*
1. Handle Loading or whatever necessary before the request
2. Calculate the previous index
3. Perform the request of previous data
4. Handle success or error result
5. Handle Loading or whatever necessary after the request
*/
override fun loadAfter(
param: LoadParams<Long>, callback:
LoadCallback<Long, Article>
)
/*
1. Handle Loading or whatever necessary before the request
2. Calculate the next index
3. Perform the request of previous data
4. Handle success or error result
5. Handle Loading or whatever necessary after the request
*/

Google Siento que todavía hay mucho margen de mejora y muchos otros aspectos pueden simplificar y simplificar nuestra implementación.Los resultados son Página 3, Además de poder interactuar con flujo con Datos en tiempo realEn la actualidad, es realmente cómodo utilizar el mecanismo de paginación en nuestro proyecto.Como muestra única, la anterior fuente de datos La implementación se convierte en un método:

override suspend fun load(params: LoadParams<Int>): PagingSource.LoadResult<Int, Response> 
/*
1. Handle whatever necessary before the request
2. Calculate next and previous index
3. Perform request
4. Handle success or error result
5. Handle whatever necessary after the request
*/

Paginación 3: crear un componente de paginación universal de flujo

Al presentar las funciones y componentes principales Página 3, La idea es construir un componente de paginación universal que se pueda reutilizar en el mismo proyecto, con diferentes tipos de listas.

Como se mencionó anteriormente, el componente de datos responsable de ejecutar todas las solicitudes de red o base de datos debe descargarse de Fuente de paginación Clase abstracta. Nuestro componente también será abstracto para que otros repositorios puedan usarlo:

abstract class PagingRepository<Response : Any> : PagingSource<Int, Response>()

La superclase necesita implementar dos métodos:

getRefreshKey ()

Devuelve la siguiente clave que se utilizará para cargar solicitudes posteriores.Activado antes de este método carga() Y pasar Cargar parámetro distancia.

Como clave a utilizar en nuestro componente, podemos utilizar Status.anchorPosition, Que devolverá el índice visitado más recientemente en la lista. Cuando el tipo de paso utilizado como indicador de página es un número entero, Status.anchorPosition Debe ser el valor de retorno.

override fun getRefreshKey(state: PagingState<Int, Response>): Int? 
return state.anchorPosition

carga()

Su propósito es cargar datos calculando los índices siguientes y anteriores y usándolos para realizar solicitudes de red o base de datos.Debería estar en un Fuente de paginación.Los datos deben pasar un Cargar la página de resultados Cuando tiene éxito, y El resultado de carga es incorrecto Cuando algo sale mal.

establecer uno Cargar la página de resultados, Tenemos que proporcionar Siguiente clave, Si el usuario continúa desplazándose hacia abajo, Clave anterior, En caso de que el usuario cargue los datos previamente descartados, finalmente, Una lista Contiene los datos que se mostrarán:

LoadResult.Page(
data = result,
nextKey = nextKey,
prevKey = previousKey
)

Como era de esperar, la solicitud recuperará la lista.Lo único que debemos agregar es que si hay algún problema, debemos emitir un El resultado de carga es incorrectoPor lo tanto, todo el código load () se incluirá en un bloque try / catch:

} catch (e: Exception) 
LoadResult.Error(e)

En cuanto a las claves siguientes y anteriores, deben pasarse utilizando el parámetro clave (de getRefreshKey ()).

Para calcular la siguiente clave, consideramos la lista devuelta.Si la solicitud va bien y tenemos más elementos para mostrar, la clave será Clave + 1. Si no hay más artículos, podemos devolver el artículo. Valor nulo La biblioteca entenderá que este es el final de la lista y detendrá la solicitud:

val nextKey = if (list.isEmpty()) 
if (currentPage == 0)
throw UnexpectedException()
else
null

else
currentPage + 1

Del mismo modo, se calculará la clave anterior, por ejemplo:

val previousKey = if (currentPage == START_PAGE_INDEX) null else currentPage - 1

Es importante recordar carga() Solo se activa una vez Buscapersonas Fue construido.Una especie Buscapersonas Es el punto de entrada para la paginación.Excepto uno Configuración de paginación, La clase responsable de llevar información detallada sobre el comportamiento de la búsqueda, Buscapersonas Necesito proporcionar Fuente de paginación Se utilizará para ejecutar su solicitud:

private fun getDefaultPagingConfig(): PagingConfig 
return PagingConfig(
prefetchDistance = pageSize / 2,
pageSize = pageSize,
enablePlaceholders = false
)

Anunciado Configuración de paginación, Esto Buscapersonas El constructor se verá así:

return Pager(
config = getDefaultPagingConfig(),
pagingSourceFactory = this
).flow

La mayor parte de la lógica ahora está completa. Lo que necesitamos ahora es proporcionar una forma de activar la primera solicitud de la clase que se heredará, en cuyo caso será un repositorio.Declarar un llevado a cabo() El método y pasar la solicitud de API como parámetro pueden funcionar bien:

private lateinit var call: (suspend (currentPage: Int) -> Flow<List<Response>>)protected fun execute(
call: (suspend (currentPage: Int) -> Flow<List<Response>>)
): Flow<PagingData<Response>>
this.call = call
return Pager(
config = getDefaultPagingConfig(),
pagingSourceFactory = this
).flow

Recopile todo el código, excepto algunos detalles, esta será la clase abstracta completa:

import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingSource
import androidx.paging.PagingState
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.single
/**
* Generic PagingSource component that handles performing paginated api requests.
*
* [Response] is the domain return type for the api call
*/
abstract class PagingRepository<Response : Any> : PagingSource<Int, Response>() {
protected open val pageSize: Int = DEFAULT_PAGE_SIZE

private lateinit var call: (suspend (currentPage: Int) -> Flow<List<Response>>)

private fun getDefaultPagingConfig(): PagingConfig
return PagingConfig(
prefetchDistance = pageSize / 2,
pageSize = pageSize,
enablePlaceholders = false
)
protected fun execute(
call: (suspend (currentPage: Int) -> Flow<List<Response>>)
): Flow<PagingData<Response>>
this.call = call
return Pager(
config = getDefaultPagingConfig(),
pagingSourceFactory = this
).flow
override fun getRefreshKey(state: PagingState<Int, Response>): Int?
return state.anchorPosition
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Response>
val currentPage = params.key ?: START_PAGE_INDEX return try
val result = call(currentPage).single()
val nextKey = if (result.isEmpty())
if (currentPage == 0)
throw UnexpectedException()
else
null

else
currentPage + 1
val previousKey = if (currentPage == START_PAGE_INDEX) null else currentPage - 1 LoadResult.Page(
data = result,
nextKey = nextKey,
prevKey = previousKey
)
catch (e: Exception)
LoadResult.Error(e)

private companion object
const val START_PAGE_INDEX = 0
const val DEFAULT_PAGE_SIZE = 20

}

Por ejemplo, aquí hay un repositorio que utiliza el nuevo PagingRepository. Obtiene la lista de productos y la devuelve a la vista:

import androidx.paging.PagingData
import kotlinx.coroutines.flow.Flow
class ProductRepositoryImpl(
private val productApiDataSource: ProductApiDataSource,
) : PagingRepository<Product>(), ProductRepository
override val pageSize = PRODUCT_PAGE_SIZE override fun getPagingProducts(): Flow<PagingData<Product>>
return execute currentPage -> getProducts(currentPage)
fun getProducts(
currentPage: Int
): Flow<List<Product>>
return flow
emit(
productApiDataSource.getProducts(
currentPage = currentPage,
pageSize = pageSize
)
)

private companion object
const val PRODUCT_PAGE_SIZE = 48

El código final muestra un Clase base No solo con producto Escriba, como en el ejemplo anterior, pero puede ser de cualquier tipo.Debería ser muy útil, por ejemplo, en grandes Arquitectura limpia Proyectos, todos los repositorios que contienen solicitudes que utilizan el mecanismo de paginación pueden realizar sus llamadas de paginación encapsulando solo sus llamadas de paginación Interfaz del programa de aplicación versus llevado a cabo método.

LEER  7 fantásticas funciones de píxeles que otros teléfonos Android no pueden obtener

Publicaciones relacionadas

Deja una respuesta

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

Botón volver arriba