Présentation de la bibliothèque Paging 2 Inclus dans Android Jetpack.
La bibliothèque Paging vous permet de charger et d'afficher de petits morceaux de données en même temps. Le chargement de données partielles à la demande réduit l'utilisation de la bande passante du réseau et des ressources système.
Ce guide fournit plusieurs exemples conceptuels de la bibliothèque, ainsi qu'un aperçu de son fonctionnement. Pour voir des exemples complets illustrant le fonctionnement de cette bibliothèque, accédez à l'atelier de programmation et consultez des exemples dans la section Autres ressources.
Configuration
Pour importer des composants Paging dans votre application Android, ajoutez les dépendances suivantes au fichier build.gradle
de votre application :
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 }
Architecture de la bibliothèque
Cette section décrit et présente les principaux composants de la bibliothèque Paging.
PagedList
Le composant clé de la bibliothèque Paging correspond à la classe PagedList
, qui charge des fragments de données, ou des pages de votre application. À mesure que des données supplémentaires sont nécessaires, elles sont paginées dans l'objet PagedList
existant. En cas de modification des données chargées, une nouvelle instance de PagedList
est émise vers le conteneur de données observables depuis un objet basé sur LiveData
ou RxJava2. Lorsque des objets PagedList
sont générés, l'interface utilisateur de votre application présente son contenu, tout en respectant les cycles de vie de vos contrôleurs d'interface utilisateur.
L'extrait de code suivant montre comment configurer le modèle de visualisation de votre application pour charger et présenter des données à l'aide d'un conteneur LiveData
d'objets 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(); } }
Données
Chaque instance de PagedList
charge un instantané à jour des données de votre application à partir de l'objet DataSource
correspondant. Les données sont acheminées du backend ou de la base de données de votre application vers l'objet PagedList
.
L'exemple suivant utilise la Bibliothèque de persistance Room pour organiser les données de votre application, mais si vous souhaitez les stocker par un autre moyen, vous pouvez également fournir votre propre fabrique de source de données.
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(); }
Pour savoir comment charger des données dans des objets PagedList
, consultez le guide sur le chargement des données paginées.
Interface utilisateur
La classe PagedList
fonctionne avec un objet PagedListAdapter
pour charger des éléments dans un objet RecyclerView
. Ces classes fonctionnent en synergie pour récupérer et afficher le contenu lors de son chargement, en préchargeant le contenu hors écran et en animant les modifications de contenu.
Pour en savoir plus, consultez le guide sur l'affichage des listes paginées.
Assurer la compatibilité avec différentes architectures de données
La bibliothèque Paging est compatible avec les architectures de données suivantes :
- Diffusées uniquement à partir d'un serveur backend.
- Stockées uniquement dans une base de données sur l'appareil.
- Association des autres sources utilisant la base de données sur l'appareil en tant que cache.
La figure 1 montre comment les données circulent dans chacune de ces configurations d'architecture. Dans le cas d'une solution exclusivement basée sur un réseau ou une base de données, les données sont transmises directement au modèle d'interface utilisateur de votre application. Si vous utilisez une approche combinée, les données sont transmises depuis votre serveur backend vers une base de données sur l'appareil, puis vers le modèle d'interface utilisateur de votre application. De temps en temps, le point de terminaison de chaque flux de données manque de données à charger, et demande des données supplémentaires au composant qui a fourni les données. Par exemple, lorsqu'une base de données sur appareil manque de données, elle demande plus de données au serveur.
Le reste de cette section fournit des recommandations pour configurer chaque cas d'utilisation de flux de données.
Réseau uniquement
Pour afficher les données d'un serveur backend, utilisez la version synchrone de l'API Retrofit pour charger des informations dans votre propre objet DataSource
personnalisé.
Base de données uniquement
Configurez votre RecyclerView
pour observer le stockage local, de préférence à l'aide de la bibliothèque de persistance Room. Ainsi, chaque fois que des données sont insérées ou modifiées dans la base de données de votre application, ces modifications sont automatiquement répercutées dans la RecyclerView
qui affiche ces données.
Réseau et base de données
Après avoir commencé à observer la base de données, vous pouvez utiliser PagedList.BoundaryCallback
pour savoir quand la base de données est à court de données.
Vous pouvez ensuite récupérer d'autres éléments de votre réseau et les insérer dans la base de données. Si votre interface utilisateur observe la base de données, vous n'avez rien d'autre à faire.
Gérer les erreurs réseau
Lorsque vous utilisez un réseau pour récupérer ou paginer les données que vous affichez à l'aide de la bibliothèque Paging, il est important de ne pas considérer que le réseau est soit "disponible", soit "indisponible" en permanence, car de nombreuses connexions sont intermittentes ou erratiques :
- Un serveur peut ne pas répondre à une requête réseau.
- L'appareil est peut-être connecté à un réseau lent ou faible.
Au lieu de cela, votre application doit vérifier les échecs de chaque requête et récupérer aussi efficacement que possible lorsque le réseau n'est pas disponible. Par exemple, vous pouvez ajouter un bouton "Réessayer" que les utilisateurs pourront sélectionner si l'étape d'actualisation des données ne fonctionne pas. Si une erreur se produit lors de l'étape de pagination des données, il est préférable de relancer automatiquement les requêtes de pagination.
Mettre à jour votre application existante
Si votre application consomme déjà des données à partir d'une base de données ou d'une source backend, il est possible de passer directement à une fonctionnalité fournie par la bibliothèque Paging. Cette section explique comment mettre à jour une application qui présente un design courant.
Solutions de pagination personnalisées
Si vous utilisez une fonctionnalité personnalisée pour charger de petits sous-ensembles de données à partir de la source de données de votre application, vous pouvez remplacer cette logique par celle de la classe PagedList
. Les instances de PagedList
offrent des connexions intégrées aux sources de données courantes. Ces instances fournissent également des adaptateurs pour les objets RecyclerView
que vous pouvez inclure dans l'interface utilisateur de votre application.
Données chargées à l'aide de listes au lieu de pages
Si vous utilisez une liste en mémoire comme structure de sauvegarde de données pour l'adaptateur de votre interface utilisateur, envisagez d'observer des mises à jour de données à l'aide d'une classe PagedList
si le nombre d'éléments peut s'afficher en grand. Les instances de PagedList
peuvent utiliser LiveData<PagedList>
ou Observable<List>
pour transmettre les mises à jour de données à l'interface utilisateur de votre application, ce qui réduit les temps de chargement et l'utilisation de la mémoire. Mieux encore, le remplacement d'un objet List
par un objet PagedList
dans votre application ne nécessite aucune modification de la structure de l'interface utilisateur ni de la logique de mise à jour des données.
Associer un curseur de données à une vue de liste à l'aide de CursorAdapter
Votre application peut utiliser un CursorAdapter
pour associer les données d'un Cursor
à une ListView
. Dans ce cas, vous devez généralement effectuer la migration depuis une ListView
vers une RecyclerView
comme conteneur de l'interface utilisateur de la liste des applications, puis remplacer le composant Cursor
par Room ou PositionalDataSource
, selon que les instances de Cursor
accèdent à une base de données SQLite.
Dans certains cas, par exemple lorsque vous travaillez avec des instances de Spinner
, vous ne fournissez que l'adaptateur lui-même. Une bibliothèque prend ensuite les données chargées dans cet adaptateur et les affiche pour vous. Dans ce cas, remplacez le type de données de votre adaptateur par LiveData<PagedList>
, puis encapsulez cette liste dans un objet ArrayAdapter
avant d'essayer de faire en sorte qu'une classe de bibliothèque gonfle ces éléments dans une interface utilisateur.
Charger du contenu de manière asynchrone à l'aide d'AsyncListUtil
Si vous utilisez des objets AsyncListUtil
pour charger et afficher des groupes d'informations de manière asynchrone, la bibliothèque Paging vous permet de charger des données plus facilement :
- Vos données n'ont pas besoin d'être positionnées. La bibliothèque Paging vous permet de charger des données directement à partir de votre backend à l'aide de clés fournies par le réseau.
- Vos données peuvent être extrêmement volumineuses. Avec la bibliothèque Paging, vous pouvez charger des données dans des pages jusqu'à ce qu'il ne reste plus aucune donnée.
- Vous pouvez observer vos données plus facilement. La bibliothèque Paging peut présenter les données que le ViewModel de votre application contient dans une structure de données observable.
Exemples de bases de données
Les extraits de code suivants montrent plusieurs façons de faire fonctionner tous les éléments ensemble.
Observer les données paginées avec LiveData
L'extrait de code suivant montre tous les composants qui fonctionnent ensemble. À mesure que des événements de concert sont ajoutés, supprimés ou modifiés dans la base de données, le contenu de la RecyclerView
est mis à jour automatiquement et efficacement :
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); } }; }
Observer les données paginées à l'aide de RxJava2
Si vous préférez utiliser RxJava2 au lieu de LiveData
, vous pouvez créer un objet Observable
ou 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(); } }
Vous pouvez ensuite démarrer et arrêter l'observation des données à l'aide du code de l'extrait suivant :
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(); } }
Le code pour ConcertDao
et ConcertAdapter
est le même pour une solution basée sur RxJava2 et pour une solution basée sur LiveData
.
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
- Afficher des listes paginées
- Recueillir des données paginées