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 :
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(); }
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 :
- Taille de page : nombre d'éléments sur chaque page.
- Distance de préchargement : nombre d'éléments au-delà du dernier élément visible dans l'UI d'une application que la bibliothèque Paging doit tenter d'extraire à l'avance. Cette valeur doit être plusieurs fois supérieure à la taille de la page.
- Présence d'espaces réservés : détermine si l'UI affiche des espaces réservés pour les éléments de liste qui n'ont pas encore fini de charger. Pour en savoir plus sur les avantages et les inconvénients liés à l'utilisation des espaces réservés, découvrez comment ajouter des espaces réservés dans votre interface utilisateur.
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 :
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 jetonnextPage
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
- Android Jetpack : gérez des listes infinies avec RecyclerView et Paging (Google I/O 2018)
- Android Jetpack : Paging
Recommandations personnalisées
- Remarque : Le texte du lien s'affiche lorsque JavaScript est désactivé
- Effectuer une migration vers Paging 3
- Présentation de la bibliothèque Paging 2
- Afficher des listes paginées