Descripción general de la biblioteca de Paging 2 Parte de Android Jetpack
La biblioteca de Paging te ayuda a cargar y mostrar pequeños fragmentos de datos a la vez. La carga de datos parciales a pedido reduce el uso del ancho de banda de la red y los recursos del sistema.
En esta guía, se proporcionan varios ejemplos conceptuales de la biblioteca, junto con una descripción general de cómo funciona. Para ver ejemplos completos del funcionamiento de esta biblioteca, prueba el codelab y los ejemplos de la sección de recursos adicionales.
Configuración
Si quieres importar componentes de Paging a tu app para Android, agrega las siguientes dependencias al archivo build.gradle
de tu app:
Groovy
dependencies { def paging_version = "2.1.2" implementation "androidx.paging:paging-runtime:$paging_version" // For Kotlin use paging-runtime-ktx // alternatively - without Android dependencies for testing testImplementation "androidx.paging:paging-common:$paging_version" // For Kotlin use paging-common-ktx // optional - RxJava support implementation "androidx.paging:paging-rxjava2:$paging_version" // For Kotlin use paging-rxjava2-ktx }
Kotlin
dependencies { val paging_version = "2.1.2" implementation("androidx.paging:paging-runtime:$paging_version") // For Kotlin use paging-runtime-ktx // alternatively - without Android dependencies for testing testImplementation("androidx.paging:paging-common:$paging_version") // For Kotlin use paging-common-ktx // optional - RxJava support implementation("androidx.paging:paging-rxjava2:$paging_version") // For Kotlin use paging-rxjava2-ktx }
Arquitectura de biblioteca
En esta sección, se describen y se muestran los componentes principales de la biblioteca de paginación.
PagedList
El principal componente de la biblioteca de Paging es la clase PagedList
, que carga fragmentos de los datos, o páginas, de tu app. A medida que se necesitan más datos, se paginan en el objeto PagedList
existente. Si algún dato cargado cambia, se emite una nueva instancia de PagedList
al contenedor de datos observable desde un objeto LiveData
o uno basado en RxJava2. A medida que se generan los objetos PagedList
, la IU de la app presenta su contenido y respeta los ciclos de vida de tus controladores de IU.
En el siguiente fragmento de código, se muestra cómo puedes configurar el modelo de vista de tu app para cargar y presentar datos mediante un contenedor de LiveData
de objetos PagedList
:
Kotlin
class ConcertViewModel(concertDao: ConcertDao) : ViewModel() { val concertList: LiveData<PagedList<Concert>> = concertDao.concertsByDate().toLiveData(pageSize = 50) }
Java
public class ConcertViewModel extends ViewModel { private ConcertDao concertDao; public final LiveData<PagedList<Concert>> concertList; // Creates a PagedList object with 50 items per page. public ConcertViewModel(ConcertDao concertDao) { this.concertDao = concertDao; concertList = new LivePagedListBuilder<>( concertDao.concertsByDate(), 50).build(); } }
Datos
Cada instancia de PagedList
carga un resumen actualizado de los datos de tu app desde el objeto DataSource
correspondiente. Los datos fluyen desde el backend o la base de datos de la app al objeto PagedList
.
En el ejemplo siguiente, se usa la biblioteca de persistencias Room para organizar los datos de tu app. Sin embargo, si deseas almacenar los datos por otros medios, también puedes proporcionar tu propia fuente de datos.
Kotlin
@Dao interface ConcertDao { // The Int type parameter tells Room to use a PositionalDataSource object. @Query("SELECT * FROM concerts ORDER BY date DESC") fun concertsByDate(): DataSource.Factory<Int, Concert> }
Java
@Dao public interface ConcertDao { // The Integer type parameter tells Room to use a // PositionalDataSource object. @Query("SELECT * FROM concerts ORDER BY date DESC") DataSource.Factory<Integer, Concert> concertsByDate(); }
Para obtener más información sobre cómo cargar datos en objetos PagedList
, consulta la guía sobre cómo cargar datos paginados.
IU
La clase PagedList
funciona con un PagedListAdapter
para cargar elementos en un RecyclerView
. Estas clases trabajan juntas para recuperar y mostrar contenido a medida que se carga, y proporcionan una carga previa del contenido fuera de la vista y animan los cambios de contenido.
Para obtener más información, consulta la guía sobre cómo mostrar listas paginadas.
Admite diferentes arquitecturas de datos
La biblioteca de paginación admite las siguientes arquitecturas de datos:
- Publicada solo desde un servidor de backend.
- Almacenada solo en una base de datos en el dispositivo.
- Una combinación de las otras fuentes usando la base de datos en el dispositivo como caché.
En la figura 1, se muestra cómo fluyen los datos en cada uno de estos escenarios de arquitectura. En el caso de una solución solo de red o de base de datos, los datos fluyen directamente al modelo de IU de tu app. Si usas un enfoque combinado, los datos fluyen desde el servidor de backend a una base de datos en el dispositivo y, luego, al modelo de IU de tu app. De vez en cuando, el extremo de cada flujo de datos se queda sin datos para cargar. En ese momento, solicita más datos al componente que los proporcionó. Por ejemplo, cuando una base de datos en el dispositivo se queda sin datos, solicita más al servidor.
![Diagramas de flujos de datos](https://developer.android.com/static/topic/libraries/architecture/images/paging-library-data-flow.png?authuser=5&hl=es)
En el resto de esta sección, se proporcionan recomendaciones para configurar cada caso de uso del flujo de datos.
Solo de red
Para mostrar datos de un servidor de backend, usa la versión síncrona de la API de Retrofit a fin de cargar información en tu propio objeto DataSource
personalizado.
Solo de base de datos
Configura tu RecyclerView
para observar el almacenamiento local. De preferencia, usa la biblioteca de persistencias Room. De esa manera, cada vez que se insertan o se modifican datos en la base de datos de la app, estos cambios se reflejan automáticamente en la RecyclerView
que muestra los datos.
Red y base de datos
Después de comenzar a observar la base de datos, puedes escuchar cuándo la base de datos no tiene datos utilizando PagedList.BoundaryCallback
.
Luego, puedes recuperar más elementos de la red e insertarlos en la base de datos. Si tu IU está observando la base de datos, eso es todo lo que tienes que hacer.
Maneja errores de red
Cuando usas una red para recuperar o paginar los datos que muestras por medio de la biblioteca de Paging, es importante no tratar la red como "disponible" o "no disponible" todo el tiempo, ya que muchas conexiones son intermitentes o inestables:
- Es posible que un servidor en particular no responda a una solicitud de red.
- El dispositivo puede estar conectado a una red lenta o débil.
En cambio, tu app debe verificar cada solicitud para ver si hubo fallas y recuperarse de la mejor manera posible en los casos en que la red no esté disponible. Por ejemplo, puedes proporcionar un botón de "reintentar" para que los usuarios seleccionen si el paso de actualización de datos no funciona. Si se produce un error en el paso de paginación de datos, es mejor volver a intentar las solicitudes de paginación automáticamente.
Actualiza tu app existente
Si tu app ya consume datos de una base de datos o una fuente de backend, es posible actualizar directamente a la funcionalidad que proporciona la biblioteca de Paging. En esta sección, se muestra cómo actualizar una app que tiene un diseño común existente.
Soluciones de paginación personalizadas
Si utilizas una funcionalidad personalizada para cargar pequeños subconjuntos de datos desde la fuente de datos de la app, puedes reemplazar esta lógica con la de la clase PagedList
. Las instancias de PagedList
ofrecen conexiones integradas a fuentes de datos comunes. Estas instancias también proporcionan adaptadores para objetos RecyclerView
que puedes incluir en la IU de tu app.
Uso de listas en lugar de páginas para la carga de datos
Si usas una lista en la memoria como la estructura de datos de copia de seguridad para el adaptador de la IU, considera observar las actualizaciones de datos por medio de una clase PagedList
en caso de que el número de elementos de la lista se vuelva grande. Las instancias de PagedList
pueden usar LiveData<PagedList>
o Observable<List>
para pasar actualizaciones de datos a la IU de tu app, lo que minimiza los tiempos de carga y el uso de memoria. Mejor aún, el reemplazo de un objeto List
con un objeto PagedList
en tu app no requiere ningún cambio en la estructura de la IU ni en la lógica de actualización de datos de tu app.
Usa CursorAdapter para asociar un cursor de datos con una vista de lista
Es posible que tu app use CursorAdapter
para asociar datos de un Cursor
con un ListView
. En ese caso, generalmente necesitas migrar de una ListView
a una RecyclerView
como el contenedor de IU de la lista de la app y, luego, reemplazar el componente Cursor
con Room o PositionalDataSource
, según si las instancias de Cursor
acceden a una base de datos SQLite.
En algunas situaciones, como cuando trabajas con instancias de Spinner
, solo proporcionas el adaptador. Luego, una biblioteca toma los datos cargados en ese adaptador y los muestra por ti. En esas situaciones, cambia el tipo de datos del adaptador a LiveData<PagedList>
y, luego, une esta lista en un objeto ArrayAdapter
antes de intentar que una clase de biblioteca los aumente estos elementos en una IU.
Usa AsyncListUtil para cargar contenido de forma asíncrona
Si usas objetos AsyncListUtil
para cargar y mostrar grupos de información de manera asíncrona, la biblioteca de Paging te permitirá cargar datos más fácilmente:
- No es necesario que los datos sean posicionales. La biblioteca de Paging te permite cargar datos directamente desde tu backend mediante las claves que proporciona la red.
- Los datos pueden ser infinitamente grandes. Con la biblioteca de Paging, puedes cargar datos en las páginas hasta que no queden más por cargar.
- Los datos se pueden observar con más facilidad. La biblioteca de Paging puede presentar los datos que tiene el ViewModel de tu app en una estructura de datos observable.
Ejemplos de bases de datos
En los siguientes fragmentos de código, se muestran varias formas posibles de hacer que todas las piezas funcionen juntas.
Usa LiveData para observar datos paginados
En el siguiente fragmento de código, se muestran todas las piezas en funcionamiento. A medida que se agregan, quitan o cambian eventos de concierto en la base de datos, el contenido de RecyclerView
se actualiza eficiente y automáticamente:
Kotlin
@Dao interface ConcertDao { // The Int type parameter tells Room to use a PositionalDataSource // object, with position-based loading under the hood. @Query("SELECT * FROM concerts ORDER BY date DESC") fun concertsByDate(): DataSource.Factory<Int, Concert> } class ConcertViewModel(concertDao: ConcertDao) : ViewModel() { val concertList: LiveData<PagedList<Concert>> = concertDao.concertsByDate().toLiveData(pageSize = 50) } class ConcertActivity : AppCompatActivity() { public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Use the 'by viewModels()' Kotlin property delegate // from the activity-ktx artifact val viewModel: ConcertViewModel by viewModels() val recyclerView = findViewById(R.id.concert_list) val adapter = ConcertAdapter() viewModel.concertList.observe(this, PagedList(adapter::submitList)) recyclerView.setAdapter(adapter) } } class ConcertAdapter() : PagedListAdapter<Concert, ConcertViewHolder>(DIFF_CALLBACK) { fun onBindViewHolder(holder: ConcertViewHolder, position: Int) { val concert: Concert? = getItem(position) // Note that "concert" is a placeholder if it's null. holder.bindTo(concert) } companion object { private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Concert>() { // Concert details may have changed if reloaded from the database, // but ID is fixed. override fun areItemsTheSame(oldConcert: Concert, newConcert: Concert) = oldConcert.id == newConcert.id override fun areContentsTheSame(oldConcert: Concert, newConcert: Concert) = oldConcert == newConcert } } }
Java
@Dao public interface ConcertDao { // The Integer type parameter tells Room to use a PositionalDataSource // object, with position-based loading under the hood. @Query("SELECT * FROM concerts ORDER BY date DESC") DataSource.Factory<Integer, Concert> concertsByDate(); } public class ConcertViewModel extends ViewModel { private ConcertDao concertDao; public final LiveData<PagedList<Concert>> concertList; public ConcertViewModel(ConcertDao concertDao) { this.concertDao = concertDao; concertList = new LivePagedListBuilder<>( concertDao.concertsByDate(), /* page size */ 50).build(); } } public class ConcertActivity extends AppCompatActivity { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); ConcertViewModel viewModel = new ViewModelProvider(this).get(ConcertViewModel.class); RecyclerView recyclerView = findViewById(R.id.concert_list); ConcertAdapter adapter = new ConcertAdapter(); viewModel.concertList.observe(this, adapter::submitList); recyclerView.setAdapter(adapter); } } public class ConcertAdapter extends PagedListAdapter<Concert, ConcertViewHolder> { protected ConcertAdapter() { super(DIFF_CALLBACK); } @Override public void onBindViewHolder(@NonNull ConcertViewHolder holder, int position) { Concert concert = getItem(position); if (concert != null) { holder.bindTo(concert); } else { // Null defines a placeholder item - PagedListAdapter automatically // invalidates this row when the actual object is loaded from the // database. holder.clear(); } } private static DiffUtil.ItemCallback<Concert> DIFF_CALLBACK = new DiffUtil.ItemCallback<Concert>() { // Concert details may have changed if reloaded from the database, // but ID is fixed. @Override public boolean areItemsTheSame(Concert oldConcert, Concert newConcert) { return oldConcert.getId() == newConcert.getId(); } @Override public boolean areContentsTheSame(Concert oldConcert, Concert newConcert) { return oldConcert.equals(newConcert); } }; }
Usa RxJava2 para observar datos paginados
Si prefieres usar RxJava2 en lugar de LiveData
, puedes crear un objeto Observable
o Flowable
:
Kotlin
class ConcertViewModel(concertDao: ConcertDao) : ViewModel() { val concertList: Observable<PagedList<Concert>> = concertDao.concertsByDate().toObservable(pageSize = 50) }
Java
public class ConcertViewModel extends ViewModel { private ConcertDao concertDao; public final Observable<PagedList<Concert>> concertList; public ConcertViewModel(ConcertDao concertDao) { this.concertDao = concertDao; concertList = new RxPagedListBuilder<>( concertDao.concertsByDate(), /* page size */ 50) .buildObservable(); } }
Luego, puedes comenzar a observar los datos y dejar de hacerlo utilizando el código del siguiente fragmento:
Kotlin
class ConcertActivity : AppCompatActivity() { private val adapter = ConcertAdapter() // Use the 'by viewModels()' Kotlin property delegate // from the activity-ktx artifact private val viewModel: ConcertViewModel by viewModels() private val disposable = CompositeDisposable() public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val recyclerView = findViewById(R.id.concert_list) recyclerView.setAdapter(adapter) } override fun onStart() { super.onStart() disposable.add(viewModel.concertList .subscribe(adapter::submitList))) } override fun onStop() { super.onStop() disposable.clear() } }
Java
public class ConcertActivity extends AppCompatActivity { private ConcertAdapter adapter = new ConcertAdapter(); private ConcertViewModel viewModel; private CompositeDisposable disposable = new CompositeDisposable(); @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); RecyclerView recyclerView = findViewById(R.id.concert_list); viewModel = new ViewModelProvider(this).get(ConcertViewModel.class); recyclerView.setAdapter(adapter); } @Override protected void onStart() { super.onStart(); disposable.add(viewModel.concertList .subscribe(adapter.submitList(flowableList) )); } @Override protected void onStop() { super.onStop(); disposable.clear(); } }
El código de ConcertDao
y ConcertAdapter
es el mismo para una solución basada en RxJava2 que para una basada en LiveData
.
Envía comentarios
Usa estos recursos para compartir tus comentarios y tus ideas con nosotros:
- Herramienta de seguimiento de errores
- Informa los problemas para que podamos corregir los errores.
Recursos adicionales
Para obtener más información sobre la biblioteca de Paging, consulta los siguientes recursos.
Ejemplos
Codelabs
Videos
- Android Jetpack: Cómo administrar listas infinitas con RecyclerView y Paging (Google I/O 2018)
- Android Jetpack: Paging
Recomendaciones para ti
- Nota: El texto del vínculo se muestra cuando JavaScript está desactivado
- Cómo migrar a Paging 3
- Cómo mostrar listas paginadas
- Cómo recopilar datos paginados