La bibliothèque Paging permet de charger et d'afficher des données paginées à partir d'un ensemble de données plus volumineux. Ce guide explique comment utiliser la bibliothèque Paging pour configurer un flux de données paginées à partir d'une source de données réseau et l'afficher dans une liste différée.
Définir une source de données
La première étape consiste à définir une implémentation PagingSource afin d'identifier la source de données. La classe d'API PagingSource inclut la méthode load, que vous devez ignorer pour indiquer comment extraire les données paginées de la source de données correspondante.
Utilisez directement la classe PagingSource pour utiliser les coroutines Kotlin pour le chargement asynchrone.
Sélectionner des types de clés et de valeurs
PagingSource<Key, Value> comporte deux paramètres de type : Key et Value. La clé définit l'identifiant utilisé pour charger les données, tandis que la valeur correspond au type des données. Par exemple, si vous chargez des pages d'objets User à partir du réseau en transmettant des numéros de page Int à Retrofit, sélectionnez Int comme type Key et User comme type Value.
Définir la PagingSource
L'exemple suivant implémente une PagingSource qui charge les pages d'éléments par numéro de page. Le type Key est Int et le type Value est User.
class ExamplePagingSource(
val backend: ExampleBackendService,
val query: String
) : PagingSource<Int, User>() {
override suspend fun load(
params: LoadParams<Int>
): LoadResult<Int, User> {
init {
// the data source is expected to be immutable
// invalidate PagingSource if data source
// has updated
backEnd.addDatabaseOnChangedListener {
invalidate()
}
}
try {
// Start refresh at page 1 if undefined.
val nextPageNumber = params.key ?: 1
val response = backend.searchUsers(query, nextPageNumber)
return LoadResult.Page(
data = response.users,
prevKey = null, // Only paging forward.
nextKey = nextPageNumber + 1
)
} catch (e: Exception) {
// Handle errors in this block and return LoadResult.Error for
// expected errors (such as a network failure).
}
}
override fun getRefreshKey(state: PagingState<Int, User>): Int? {
// Try to find the page key of the closest page to anchorPosition from
// either the prevKey or the nextKey; you need to handle nullability
// here.
// * prevKey == null -> anchorPage is the first page.
// * nextKey == null -> anchorPage is the last page.
// * both prevKey and nextKey are null -> anchorPage is the
// initial page, so return null.
return state.anchorPosition?.let { anchorPosition ->
val anchorPage = state.closestPageToPosition(anchorPosition)
anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
}
}
}
Une implémentation PagingSource type transmet les paramètres fournis dans son constructeur à la méthode load afin de charger les données adéquates pour une requête. Dans l'exemple ci-dessus, ces paramètres sont les suivants :
backend: instance du service de backend qui fournit les données.query: requête de recherche à envoyer au service indiqué parbackend.
L'objet LoadParams contient des informations sur le chargement à effectuer. Celles-ci incluent la clé et le nombre d'éléments à charger. Pour différencier les types de chargement dans load, vérifiez quelle sous-classe de LoadParams a été transmise : LoadParams.Refresh, LoadParams.Append ou LoadParams.Prepend.
L'objet LoadResult contient le résultat du chargement. LoadResult est une classe scellée qui prend l'une des trois formes suivantes, selon que l'appel load a réussi :
- Si le chargement aboutit, renvoyez un objet
LoadResult.Page. TransmetteznullpournextKeyouprevKeysi aucune autre donnée ne peut être chargée dans cette direction. - Si le chargement échoue, renvoyez un objet
LoadResult.Error. - Si la
PagingSourcen'est plus valide, car les données sous-jacentes ont changé, renvoyez un objetLoadResult.Invalid.
La figure suivante montre comment la fonction load de cet exemple reçoit la clé pour chaque chargement et fournit la clé pour le chargement suivant.
load utilise et met à jour la clé.
L'implémentation de PagingSource doit également implémenter une méthode getRefreshKey qui accepte un objet PagingState comme paramètre. Elle renvoie la clé à transmettre à la méthode load lorsque les données sont actualisées ou invalidées après le chargement initial. La bibliothèque Paging appelle automatiquement cette méthode lors des actualisations ultérieures des données. Pour la pagination basée sur le décalage, où les clés représentent les positions des éléments, vous pouvez calculer la clé d'actualisation par rapport à state.anchorPosition :
// Only for positional or offset PagingSources where Key is a 0-based item index
// rather than a page number.
override fun getRefreshKey(state: PagingState<Int, User>): Int? {
// Center the refreshed load around the user's current viewport so the list
// does not jump back to the top on invalidation.
return state.anchorPosition?.let { anchorPosition ->
(anchorPosition - state.config.initialLoadSize / 2).coerceAtLeast(0)
}
}
Gérer les erreurs
Les requêtes de chargement de données peuvent échouer pour plusieurs raisons, en particulier en cas de chargement sur un réseau. Signalez les erreurs rencontrées lors du chargement en renvoyant un objet LoadResult.Error à partir de la méthode load.
Par exemple, vous pouvez détecter et signaler les erreurs de chargement dans la ExamplePagingSource de l'exemple précédent en ajoutant le code suivant à la méthode load :
catch (e: IOException) {
// IOException for network failures.
return LoadResult.Error(e)
} catch (e: HttpException) {
// HttpException for any non-2xx HTTP status codes.
return LoadResult.Error(e)
}
Pour en savoir plus sur la gestion des erreurs Retrofit, consultez les exemples fournis dans la documentation de référence de l'API PagingSource.
PagingSource collecte et transmet les objets LoadResult.Error à l'interface utilisateur pour que vous puissiez les exploiter. Pour savoir comment exposer l'état de chargement dans l'interface utilisateur, consultez Gérer et présenter les états de chargement.
Configurer un flux de PagingData
Vous avez ensuite besoin d'un flux de données paginées généré par l'implémentation de PagingSource.
Configurez le flux de données dans votre ViewModel. La classe Pager fournit des méthodes qui exposent un flux réactif d'objets PagingData à partir d'une PagingSource. La bibliothèque Paging expose le flux de données en tant que Flow.
Lorsque vous créez une instance Pager pour configurer votre flux réactif, indiquez-lui un objet de configuration PagingConfig et une fonction qui indique au Pager comment obtenir une instance de votre implémentation de PagingSource. Vous pouvez également spécifier un initialKey facultatif, tel que 1 pour les numéros de page commençant par 1 ou 0 pour la pagination basée sur le décalage. Dans PagingConfig, vous pouvez configurer pageSize, initialLoadSize, prefetchDistance et enablePlaceholders. La propriété prefetchDistance définit le nombre d'éléments à partir de l'un des bords du contenu chargé qui déclenchent la récupération suivante.
class UserViewModel(
private val backend: ExampleBackendService,
private val query: String
) : ViewModel() {
val userPagingFlow: Flow<PagingData<User>> = Pager(
config = PagingConfig(
pageSize = 20,
// Load 3 pages initially so the user can scroll immediately without
// hitting a loading state.
initialLoadSize = 60,
// Fetch the next page far enough ahead of the scroll position to
// avoid visible pauses.
prefetchDistance = 20,
enablePlaceholders = true
),
initialKey = 1,
pagingSourceFactory = {
ExamplePagingSource(backend, query)
}
)
.flow
.cachedIn(viewModelScope)
}
L'opérateur cachedIn rend le flux de données partageable et met en cache les données chargées avec l'élément CoroutineScope fourni. Sans cachedIn, le PagingData ne peut pas être collecté de nouveau. Cet exemple utilise le viewModelScope fourni par l'artefact lifecycle-viewmodel-ktx de cycle de vie.
L'objet Pager appelle la méthode load à partir de l'objet PagingSource en lui fournissant l'objet LoadParams et en recevant l'objet LoadResult en retour.
Collecter et afficher les données dans votre UI
Pour connecter le flux paginé à l'UI, obtenez le flux à partir de votre ViewModel et transmettez-le à votre composable de liste.
@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
val userFlow = viewModel.userPagingFlow
UserList(flow = userFlow)
}
Utilisez collectAsLazyPagingItems pour convertir le flux PagingData en LazyPagingItems. Utilisez ensuite l'API items dans un LazyColumn pour disposer chaque élément.
Veillez à fournir un identifiant unique et stable pour chaque article à l'aide de itemKey.
L'exemple suivant utilise it.id (qui fait référence à la propriété User.id), car il reste stable pour l'instance User lors des mises à jour des données.
@Composable
fun UserList(flow: Flow<PagingData<User>>) {
val lazyPagingItems = flow.collectAsLazyPagingItems()
LazyColumn {
items(
lazyPagingItems.itemCount,
key = lazyPagingItems.itemKey { it.id }
) { index ->
val user = lazyPagingItems[index]
if (user != null) {
UserRow(user)
} else {
UserPlaceholder()
}
}
}
}
La bibliothèque Paging utilise null pour les espaces réservés pendant le chargement d'une page. Par conséquent, si vous avez activé les espaces réservés, vous devez gérer les valeurs null dans le bloc de contenu.
La liste affiche désormais les données paginées, et la bibliothèque Paging charge des pages supplémentaires lorsque l'utilisateur fait défiler l'écran.
Ressources supplémentaires
Pour en savoir plus sur la bibliothèque Paging, consultez ces ressources supplémentaires :
Documentation
Contenu des vues
Recommandations personnalisées
- Remarque : Le texte du lien s'affiche lorsque JavaScript est désactivé
- Page du réseau et de la base de données
- Effectuer une migration vers Paging 3
- Présentation de la bibliothèque Paging