Recueillir des données paginées

En s'appuyant sur la présentation de la bibliothèque Paging, ce guide explique comment personnaliser la solution de chargement des données de votre application pour répondre à ses spécificités en termes d'architecture.

Créer une liste observable

Généralement, votre code d'interface utilisateur (UI) observe un objet LiveData<PagedList> (ou, si vous utilisez RxJava2, un objet Flowable<PagedList> ou Observable<PagedList>), qui se trouve dans le ViewModel de votre application. Cet objet observable associe la présentation et le contenu des données de la liste de votre application.

Pour créer l'un de ces objets PagedList observables, transmettez une instance de DataSource.Factory à un objet LivePagedListBuilder ou RxPagedListBuilder. Un objet DataSource charge les pages pour une seule PagedList. La classe de fabrique crée des instances de PagedList en réponse à des mises à jour de contenu, telles que des invalidations de table de base de données et des actualisations de réseau. La bibliothèque de persistance de Room peut vous fournir des objets DataSource.Factory, ou vous pouvez créer les vôtres.

L'extrait de code suivant montre comment créer une instance de LiveData<PagedList> dans la classe ViewModel de votre application à l'aide des fonctionnalités de création 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();

Définir votre propre configuration de pagination

Pour configurer davantage un LiveData<PagedList> pour les cas d'utilisation avancés, vous pouvez également définir votre propre configuration de pagination. Vous pouvez notamment définir les attributs suivants :

Pour un contrôle plus précis du moment où la bibliothèque Paging charge une liste à partir de la base de données de votre application, transmettez un objet Executor personnalisé à la classe LivePagedListBuilder, comme indiqué dans l'extrait de code suivant :

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

Choisir un type de source de données approprié

Il est important de vous connecter à la source de données qui gère le mieux la structure de vos données sources :

  • Utilisez PageKeyedDataSource si les pages que vous chargez présentent des touches suivant/précédent. Par exemple, si vous récupérez des posts de réseaux sociaux depuis le réseau, vous devrez peut-être transmettre un jeton nextPage d'un chargement à un chargement ultérieur.
  • Utilisez ItemKeyedDataSource si vous devez utiliser les données de l'élément N pour récupérer l'élément N+1. Par exemple, si vous récupérez les commentaires en fils d'une application de discussion, vous devrez peut-être transmettre l'ID du dernier commentaire pour obtenir le contenu du prochain commentaire.
  • Utilisez PositionalDataSource pour récupérer des pages de données à partir de n'importe quel emplacement de votre magasin de données. Cette classe permet de demander un ensemble d'éléments de données à partir de l'emplacement sélectionné. Par exemple, la requête peut renvoyer les 50 éléments de données à partir de l'emplacement 1 500.

Recevoir un avertissement en cas de données non valides

Lorsque vous utilisez la bibliothèque Paging, la couche de données est responsable d'avertir les autres couches de votre application lorsqu'une table ou une ligne est devenue obsolète. Pour ce faire, appelez invalidate() à partir de la classe DataSource que vous avez choisie pour votre application.

Créer vos propres sources de données

Si vous utilisez une solution de données locales personnalisée ou que vous chargez des données directement à partir d'un réseau, vous pouvez implémenter l'une des sous-classes DataSource. L'extrait de code suivant montre une source de données associée à l'heure de début d'un concert :

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

Vous pouvez ensuite charger ces données personnalisées dans des objets PagedList en créant une sous-classe concrète de DataSource.Factory. L'extrait de code suivant montre comment générer des instances de la source de données personnalisée définie dans l'extrait de code précédent :

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

Comprendre le fonctionnement des mises à jour de contenu

Lorsque vous créez des objets PagedList observables, réfléchissez au fonctionnement des mises à jour du contenu. Si vous chargez des données directement à partir d'une base de données Room, elles sont automatiquement transférées vers l'UI de votre application.

Lorsque vous utilisez une API de réseau paginée, vous avez généralement une interaction utilisateur, telle que "balayer pour actualiser", laquelle sert de signal pour invalider la DataSource la plus récemment utilisée. Vous demandez ensuite une nouvelle instance de cette source de données. L'extrait de code suivant illustre ce comportement :

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

Fournir un mappage de données

La bibliothèque Paging accepte les transformations basées sur un élément ou une page des éléments chargés par DataSource.

Dans l'extrait de code suivant, une combinaison du nom et de la date du concert est mappée à une chaîne unique contenant le nom et la date :

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

Cela peut être utile si vous souhaitez encapsuler, convertir ou préparer des éléments une fois qu'ils sont chargés. Ce travail ayant lieu sur l'exécuteur d'extraction, vous pouvez effectuer un travail potentiellement coûteux, comme lire un disque ou interroger une autre base de données.

Envoyer des commentaires

Faites-nous part de vos commentaires et de vos idées via les ressources suivantes :

Issue Tracker
Signalez les problèmes pour que nous puissions corriger les bugs.

Ressources supplémentaires

Pour en savoir plus sur la bibliothèque Paging, consultez les ressources suivantes.

Exemples

Ateliers de programmation

Vidéos