Recopila datos paginados

Esta guía se basa en la descripción general de la biblioteca de paginación y explica cómo personalizar la solución de carga de datos de tu app para satisfacer sus necesidades de arquitectura.

Crea una lista observable

Por lo general, el código de IU observa un objeto LiveData<PagedList> (o, si usas RxJava2, un objeto Flowable<PagedList> u Observable<PagedList>), que reside en el ViewModel de la app. Ese objeto observable forma una conexión entre la presentación y el contenido de los datos de la lista de tu app.

Para crear uno de esos objetos observables de PagedList, pasa una instancia de DataSource.Factory a un objeto LivePagedListBuilder o RxPagedListBuilder. Un objeto DataSource carga páginas para una sola PagedList. La clase factory crea instancias nuevas de PagedList en respuesta a actualizaciones de contenido, como invalidaciones de tablas de bases de datos y actualizaciones de red. La biblioteca de Room Persistence puede proporcionar objetos DataSource.Factory por ti o puedes desarrollar unos propios.

En el siguiente fragmento de código, se muestra cómo crear una instancia nueva de LiveData<PagedList> en la clase ViewModel de la app utilizando las funcionalidades de desarrollo DataSource.Factory de Room:

ConcertDao

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>
    }
    

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();
    }
    

ConcertViewModel

Kotlin

    // The Int type argument corresponds to a PositionalDataSource object.
    val myConcertDataSource : DataSource.Factory<Int, Concert> =
           concertDao.concertsByDate()

    val concertList = myConcertDataSource.toLiveData(pageSize = 50)
    

Java

    // The Integer type argument corresponds to a PositionalDataSource object.
    DataSource.Factory<Integer, Concert> myConcertDataSource =
           concertDao.concertsByDate();

    LiveData<PagedList<Concert>> concertList =
            LivePagedListBuilder(myConcertDataSource, /* page size */ 50).build();
    

Define tu propia configuración de paginación

Si quieres configurar una LiveData<PagedList> para casos avanzados, también puedes definir tu propia configuración de paginación. En especial, puedes definir los siguientes atributos:

  • Tamaño de la página: La cantidad de elementos en cada página.
  • Distancia de precarga: Dado el último elemento visible en la IU de una app, la cantidad de elementos más allá de ese último elemento que la biblioteca de paginación debería intentar cargar de antemano. Este valor debe ser varias veces mayor que el tamaño de la página.
  • Presencia del marcador de posición: Determina si la IU muestra marcadores de posición para los elementos de la lista que aún no terminaron de cargarse. Para obtener más información sobre los beneficios y las desventajas de usar marcadores de posición, consulta cómo proporcionar marcadores de posición en tu IU.

Si deseas tener más control sobre cuándo la biblioteca de paginación carga una lista desde la base de datos de tu app, pasa un objeto Executor personalizado a LivePagedListBuilder, como se muestra en el siguiente fragmento de código:

ConcertViewModel

Kotlin

    val myPagingConfig = Config(
            pageSize = 50,
            prefetchDistance = 150,
            enablePlaceholders = true
    )

    // The Int type argument corresponds to a PositionalDataSource object.
    val myConcertDataSource : DataSource.Factory<Int, Concert> =
            concertDao.concertsByDate()

    val concertList = myConcertDataSource.toLiveData(
            pagingConfig = myPagingConfig,
            fetchExecutor = myExecutor
    )
    

Java

    PagedList.Config myPagingConfig = new PagedList.Config.Builder()
            .setPageSize(50)
            .setPrefetchDistance(150)
            .setEnablePlaceholders(true)
            .build();

    // The Integer type argument corresponds to a PositionalDataSource object.
    DataSource.Factory<Integer, Concert> myConcertDataSource =
            concertDao.concertsByDate();

    LiveData<PagedList<Concert>> concertList =
            new LivePagedListBuilder<>(myConcertDataSource, myPagingConfig)
                .setFetchExecutor(myExecutor)
                .build();
    

Elige el tipo de fuente de datos correcto

Es importante conectarse a la fuente de datos que mejor maneja la estructura de datos de tu fuente:

  • Usa PageKeyedDataSource si las páginas que cargas tienen incorporadas las teclas siguiente/anterior. Por ejemplo, si cargas publicaciones de redes sociales desde la red, es posible que debas pasar un token de nextPage de una carga a una carga posterior.
  • Usa ItemKeyedDataSource si necesitas usar datos del elemento N para obtener el elemento N + 1. Por ejemplo, si cargas comentarios en conversaciones para una app de debate, es posible que debas pasar el ID del último comentario para obtener el contenido del siguiente comentario.
  • Usa PositionalDataSource si necesitas cargar páginas de datos desde cualquier ubicación que elijas en tu almacén de datos. Esta clase admite la solicitud de un conjunto de elementos de datos que comiencen desde la ubicación que selecciones. Por ejemplo, la solicitud podría mostrar los 50 elementos de datos que comienzan con la ubicación 1500.

Notifica cuando los datos no sean válidos

Cuando se usa la biblioteca de paginación, corresponde a la capa de datos notificar a las otras capas de tu app cuando una tabla o fila está inactiva. Para hacerlo, llama a invalidate() desde la clase DataSource que hayas elegido para tu app.

Crea tus propias fuentes de datos

Si usas una solución de datos local personalizada o si cargas datos directamente desde una red, puedes implementar una de las subclases de DataSource. En el siguiente fragmento de código, se muestra una fuente de datos que está desconectada de la hora de inicio de un concierto determinado:

Kotlin

    class ConcertTimeDataSource() :
            ItemKeyedDataSource<Date, Concert>() {
        override fun getKey(item: Concert) = item.startTime

        override fun loadInitial(
                params: LoadInitialParams<Date>,
                callback: LoadInitialCallback<Concert>) {
            val items = fetchItems(params.requestedInitialKey,
                    params.requestedLoadSize)
            callback.onResult(items)
        }

        override fun loadAfter(
                params: LoadParams<Date>,
                callback: LoadCallback<Concert>) {
            val items = fetchItemsAfter(
                date = params.key,
                limit = params.requestedLoadSize)
            callback.onResult(items)
        }
    }
    

Java

    public class ConcertTimeDataSource
            extends ItemKeyedDataSource<Date, Concert> {
        @NonNull
        @Override
        public Date getKey(@NonNull Concert item) {
            return item.getStartTime();
        }

        @Override
        public void loadInitial(@NonNull LoadInitialParams<Date> params,
                @NonNull LoadInitialCallback<Concert> callback) {
            List<Concert> items =
                fetchItems(params.key, params.requestedLoadSize);
            callback.onResult(items);
        }

        @Override
        public void loadAfter(@NonNull LoadParams<Date> params,
                @NonNull LoadCallback<Concert> callback) {
            List<Concert> items =
                fetchItemsAfter(params.key, params.requestedLoadSize);
            callback.onResult(items);
        }
    

Luego, puedes cargar estos datos personalizados en objetos PagedList. Para ello, crea una subclase concreta de DataSource.Factory. En el siguiente fragmento de código, se muestra cómo generar instancias nuevas de la fuente de datos personalizada definida en el fragmento de código anterior:

Kotlin

    class ConcertTimeDataSourceFactory :
            DataSource.Factory<Date, Concert>() {
        val sourceLiveData = MutableLiveData<ConcertTimeDataSource>()
        var latestSource: ConcertDataSource?
        override fun create(): DataSource<Date, Concert> {
            latestSource = ConcertTimeDataSource()
            sourceLiveData.postValue(latestSource)
            return latestSource
        }
    }
    

Java

    public class ConcertTimeDataSourceFactory
            extends DataSource.Factory<Date, Concert> {
        private MutableLiveData<ConcertTimeDataSource> sourceLiveData =
                new MutableLiveData<>();

        private ConcertDataSource latestSource;

        @Override
        public DataSource<Date, Concert> create() {
            latestSource = new ConcertTimeDataSource();
            sourceLiveData.postValue(latestSource);
            return latestSource;
        }
    }
    

Considera cómo funcionan las actualizaciones de contenido

A medida que desarrollas objetos de PagedList observables, considera cómo funcionan las actualizaciones de contenido. Si cargas datos directamente desde una base de datos de Room, las actualizaciones se envían de forma automática a la IU de tu app.

Cuando se utiliza una API de red paginada, en general, tienes una interacción de usuario, como "deslizar para actualizar", que sirve como señal para invalidar el DataSource que usaste más recientemente. Luego, solicitas una instancia nueva de esa fuente de datos. En el siguiente fragmento de código, se demuestra este comportamiento:

Kotlin

    class ConcertActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            // ...
            concertTimeViewModel.refreshState.observe(this, Observer {
                // Shows one possible way of triggering a refresh operation.
                swipeRefreshLayout.isRefreshing =
                        it == MyNetworkState.LOADING
            })
            swipeRefreshLayout.setOnRefreshListener {
                concertTimeViewModel.invalidateDataSource()
            }
        }
    }

    class ConcertTimeViewModel(firstConcertStartTime: Date) : ViewModel() {
        val dataSourceFactory = ConcertTimeDataSourceFactory(firstConcertStartTime)
        val concertList: LiveData<PagedList<Concert>> =
                dataSourceFactory.toLiveData(
                    pageSize = 50,
                    fetchExecutor = myExecutor
                )

        fun invalidateDataSource() =
                dataSourceFactory.sourceLiveData.value?.invalidate()
    }
    

Java

    public class ConcertActivity extends AppCompatActivity {
        @Override
        public void onCreate(@Nullable Bundle savedInstanceState) {
            // ...
            viewModel.getRefreshState()
                    .observe(this, new Observer<NetworkState>() {
                // Shows one possible way of triggering a refresh operation.
                @Override
                public void onChanged(@Nullable MyNetworkState networkState) {
                    swipeRefreshLayout.isRefreshing =
                            networkState == MyNetworkState.LOADING;
                }
            };

            swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshListener() {
                @Override
                public void onRefresh() {
                    viewModel.invalidateDataSource();
                }
            });
        }
    }

    public class ConcertTimeViewModel extends ViewModel {
        private LiveData<PagedList<Concert>> concertList;
        private DataSource<Date, Concert> mostRecentDataSource;

        public ConcertTimeViewModel(Date firstConcertStartTime) {
            ConcertTimeDataSourceFactory dataSourceFactory =
                    new ConcertTimeDataSourceFactory(firstConcertStartTime);
            mostRecentDataSource = dataSourceFactory.create();
            concertList = new LivePagedListBuilder<>(dataSourceFactory, 50)
                    .setFetchExecutor(myExecutor)
                    .build();
        }

        public void invalidateDataSource() {
            mostRecentDataSource.invalidate();
        }
    }
    

Proporciona asignación de datos

La biblioteca de paginación admite transformaciones basadas en elementos y basadas en páginas de elementos cargados por una DataSource.

En el siguiente fragmento de código, se asigna una combinación de nombre y fecha de concierto a una sola string que contiene tanto el nombre como la fecha:

Kotlin

    class ConcertViewModel : ViewModel() {
        val concertDescriptions : LiveData<PagedList<String>>
            init {
                val concerts = database.allConcertsFactory()
                        .map "${it.name} - ${it.date}" }
                        .toLiveData(pageSize = 50)
            }
        }
    }
    

Java

    public class ConcertViewModel extends ViewModel {
        private LiveData<PagedList<String>> concertDescriptions;

        public ConcertViewModel(MyDatabase database) {
            DataSource.Factory<Integer, Concert> factory =
                    database.allConcertsFactory().map(concert ->
                        concert.getName() + "-" + concert.getDate());
            concertDescriptions = new LivePagedListBuilder<>(
                factory, /* page size */ 50).build();
        }
    }
    

Esto puede ser útil si deseas unir, convertir o preparar elementos después de cargarlos. Debido a que esos procesos se realizan en el ejecutor de carga, puedes hacer un trabajo potencialmente costoso, como leer desde el disco o consultar una base de datos separada.

Envía comentarios

Comparte tus comentarios e ideas con nosotros por medio de estos recursos:

Seguimiento de problemas
Informa los problemas para que podamos solucionar los errores.

Recursos adicionales

Para obtener más información sobre la biblioteca de paginación, consulta los siguientes recursos.

Ejemplos

Codelabs

Videos