0Pricing
Android Academy · Leçon

PagingSource et Pager

Définissez le chargement des pages.

PagingSource et Pager est une leçon Android Academy gratuite sur CoddyKit. Ceci est la leçon 2 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage Android Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours Android Academy comprend 4 leçons au total.

Définir le chargement des pages

Pour effectuer une pagination depuis une API réseau, vous écrivez un PagingSource. Celui-ci répond à deux questions pour Paging 3 :

  • Comment charger la page correspondant à une clé donnée ?
  • Si l’utilisateur actualise la liste, à partir de quel endroit dois-je reprendre ?

Vous l’enveloppez ensuite dans un Pager qui le transforme en Flow<PagingData>.

Paramètres de type de PagingSource

PagingSource<Key, Value> accepte deux paramètres de type :

  • Key - identifie une page. Pour une API utilisant des numéros de page, il s’agit de Int ; pour une API utilisant des curseurs, il s’agit d’un jeton String.
  • Value - le type d’élément, par exemple Article.
import androidx.paging.PagingSource
import androidx.paging.PagingState

class ArticlePagingSource(
    private val api: ArticleApi
) : PagingSource<Int, Article>() {
    // implement load() and getRefreshKey()
}

Implémenter load()

load() est une fonction suspend. Elle reçoit params.key (la page à récupérer) et renvoie un LoadResult.

En cas de réussite, vous renvoyez LoadResult.Page avec les éléments ainsi que les clés précédente et suivante. Une valeur null pour une clé signifie qu’il n’existe aucune page dans cette direction.

override suspend fun load(
    params: LoadParams<Int>
): LoadResult<Int, Article> {
    val page = params.key ?: 1   // first load has a null key
    return try {
        val response = api.getArticles(page = page, size = params.loadSize)
        LoadResult.Page(
            data = response.items,
            prevKey = if (page == 1) null else page - 1,
            nextKey = if (response.items.isEmpty()) null else page + 1
        )
    } catch (e: Exception) {
        LoadResult.Error(e)
    }
}

Pourquoi prevKey et nextKey sont importants

Paging utilise nextKey pour charger les pages suivantes lorsque l’utilisateur fait défiler la liste vers le bas, et prevKey pour charger les pages précédentes (ce qui est utile lorsque l’on commence au milieu d’une liste).

Renvoyer null pour nextKey indique à Paging qu’il n’y a plus de pages et qu’il doit cesser ses demandes. Oublier cela peut provoquer une boucle infinie de demandes vides.

// Stop forward paging when the server returns an empty page
nextKey = if (response.items.isEmpty()) null else page + 1

// Stop backward paging at the first page
prevKey = if (page == 1) null else page - 1

Implémenter getRefreshKey()

Lorsque la liste est actualisée (actualisation par balayage ou invalidation), Paging doit savoir quelle page recharger afin que l’utilisateur reste à peu près au même endroit.

getRefreshKey() utilise la valeur actuelle de anchorPosition - l’élément le plus proche de la zone d’affichage - pour choisir une clé pertinente.

override fun getRefreshKey(state: PagingState<Int, Article>): Int? {
    return state.anchorPosition?.let { anchor ->
        val closestPage = state.closestPageToPosition(anchor)
        closestPage?.prevKey?.plus(1)
            ?: closestPage?.nextKey?.minus(1)
    }
}

Gérer les erreurs proprement

Placez l’appel réseau dans un bloc try/catch et renvoyez LoadResult.Error(e) en cas d’échec. Paging expose cet échec sous la forme d’un LoadState.Error dans l’interface utilisateur, ce qui vous permet d’afficher un bouton de nouvelle tentative.

Ne laissez jamais une exception s’échapper de load() : interceptez-la et convertissez-la en LoadResult.Error.

return try {
    val response = api.getArticles(page = page, size = params.loadSize)
    LoadResult.Page(
        data = response.items,
        prevKey = if (page == 1) null else page - 1,
        nextKey = if (response.items.isEmpty()) null else page + 1
    )
} catch (e: IOException) {        // no network
    LoadResult.Error(e)
} catch (e: HttpException) {       // non-2xx response
    LoadResult.Error(e)
}

Créer un Pager

Un Pager relie votre PagingConfig à une fabrique qui crée un nouveau PagingSource. Sa propriété .flow est un Flow<PagingData> que l’interface utilisateur collecte.

La lambda de la fabrique doit créer une source nouvelle à chaque fois, car Paging invalide et recrée la source lors d’une actualisation.

import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import kotlinx.coroutines.flow.Flow

class ArticleRepository(private val api: ArticleApi) {
    fun articleStream(): Flow<PagingData<Article>> = Pager(
        config = PagingConfig(pageSize = 20, prefetchDistance = 5),
        pagingSourceFactory = { ArticlePagingSource(api) }
    ).flow
}

cachedIn pour les ViewModels

Collecter un PagingData est une opération à usage unique : le collecter à nouveau relance le chargement. Pour survivre aux changements de configuration et permettre à plusieurs collecteurs de partager les données, mettez le flux en cache dans le viewModelScope avec cachedIn.

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.cachedIn

class ArticleViewModel(
    repo: ArticleRepository
) : ViewModel() {
    val articles = repo.articleStream()
        .cachedIn(viewModelScope)
}

loadSize et pageSize

Notez que load() lit params.loadSize, et non directement votre pageSize configuré.

Lors du tout premier chargement, Paging peut demander un bloc initial plus volumineux (contrôlé par initialLoadSize dans PagingConfig, avec une valeur par défaut correspondant à trois fois la taille d’une page). Transmettez toujours params.loadSize à votre API afin que la demande corresponde à ce que Paging attend en retour.

// Correct: respect the size Paging asks for
val response = api.getArticles(page = page, size = params.loadSize)

// PagingConfig can tune the first load:
PagingConfig(pageSize = 20, initialLoadSize = 40)

APIs fondées sur des curseurs

Toutes les APIs n’utilisent pas de numéros de page. Certaines renvoient un curseur ou un jeton indiquant la page suivante. Le modèle est identique : il suffit de remplacer le type Key par String et d’utiliser le jeton renvoyé dans la réponse.

class CursorArticleSource(
    private val api: ArticleApi
) : PagingSource<String, Article>() {
    override suspend fun load(
        params: LoadParams<String>
    ): LoadResult<String, Article> {
        val cursor = params.key   // null on first load
        return try {
            val res = api.getArticles(cursor = cursor, size = params.loadSize)
            LoadResult.Page(
                data = res.items,
                prevKey = null,            // forward-only cursor
                nextKey = res.nextCursor   // null when exhausted
            )
        } catch (e: Exception) {
            LoadResult.Error(e)
        }
    }

    override fun getRefreshKey(state: PagingState<String, Article>) = null
}

Assembler le tout

Vous disposez maintenant de toute la couche de données : un PagingSource qui charge une page, un Pager qui transmet les pages et un ViewModel qui met le flux en cache.

La couche d’interface utilisateur collecte simplement viewModel.articles — nous l’afficherons dans la prochaine leçon avec un LazyColumn Compose.

// Data layer summary
// 1. ArticlePagingSource : PagingSource<Int, Article>
// 2. Pager(config, factory).flow  -> Flow<PagingData<Article>>
// 3. ViewModel: repo.articleStream().cachedIn(viewModelScope)
// UI just collects viewModel.articles

Vérification rapide

Dans un PagingSource fondé sur des numéros de page, qu’indique le fait de renvoyer nextKey = null depuis load() ?

Récapitulatif&nbsp;: PagingSource et Pager

Vous avez créé la couche de données pour la pagination :

  • PagingSource<Key, Value> implémente load() et getRefreshKey()
  • load() renvoie LoadResult.Page avec prevKey/nextKey, ou LoadResult.Error
  • Une clé null arrête la pagination dans cette direction
  • Pager(config, factory).flow produit un Flow<PagingData>
  • cachedIn(viewModelScope) conserve les données lors des changements de configuration

Ensuite : afficher ce flux dans une liste Compose.

Questions Fréquemment Posées

La leçon « PagingSource et Pager » est-elle gratuite ?

Oui — le texte complet de « PagingSource et Pager » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours Android Academy, passe à CoddyKit PRO. Le cours Android Academy comprend 4 leçons au total.

Qu'est-ce que j'apprendrai dans « PagingSource et Pager » ?

Définissez le chargement des pages. Tu pratiques Android Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer Android Academy ?

Aucune expérience préalable n'est requise. Android Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 2 sur 4.

Combien de temps prend la leçon « PagingSource et Pager » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon Android Academy ?

Oui. Chaque leçon Android Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. Pourquoi utiliser la pagination
  2. PagingSource et Pager
  3. Pagination dans les listes Compose
  4. RemoteMediator et mise en cache
← Retour à Android Academy