La pagination est une technique de chargement de données par pages, utilisée dans les applications mobiles et les services web pour travailler avec de grands ensembles d’enregistrements. Selon la documentation Android Developers (2025), une implémentation correcte de la pagination réduit la charge sur l’API, économise le trafic et améliore l’expérience utilisateur. Le chargement page par page permet à l’application d’afficher le contenu progressivement, sans attendre le chargement complet de toutes les données.
Points clés
La pagination (du latin pagination — division en pages) est une technique de division d’un grand ensemble de données en portions séquentielles (pages). Dans les applications mobiles, la pagination est utilisée lors du chargement de listes de messages, de flux d’actualités, de catalogues de produits, d’historique de commandes et de toute autre collection avec un nombre potentiellement illimité d’enregistrements.
Sans pagination, l’application est obligée de charger toutes les données à la fois, ce qui entraîne de longs temps d’attente, une consommation de trafic élevée et des performances instables sur les appareils faibles. Une requête API avec pagination ne renvoie qu’une seule portion de données et des méta-informations pour charger la suivante — ainsi, l’application contrôle la quantité d’informations reçues.
Les principales métriques de la pagination sont : la taille de page (page size) — nombre d’enregistrements par page (généralement 10–50), et le numéro de page ou curseur — un indicateur de la position actuelle dans l’ensemble. Le choix de la taille de page dépend du type de données : pour les éléments compacts (noms) 20–30 suffisent, pour les cartes avec images 10–15.
Les appareils mobiles ont des ressources limitées : mémoire vive, vitesse du processeur et limites de trafic. La pagination résout trois tâches clés : réduction de la consommation mémoire (seuls les éléments visibles sont stockés en mémoire), accélération du premier affichage (la première portion se charge plus vite que l’ensemble) et économie de trafic (les données ne sont chargées que lorsque l’utilisateur fait défiler la liste).
Il existe quatre types principaux de pagination, chacun résolvant des tâches spécifiques. Le choix de la méthode dépend des exigences de cohérence des données, de l’architecture API, du type de stockage et de la complexité acceptable de mise en œuvre côté client et serveur.
| Type | Principe de fonctionnement | Stabilité | Vitesse sur grands volumes |
|---|---|---|---|
| Offset | LIMIT + OFFSET en SQL | faible | diminue avec la croissance de OFFSET |
| Cursor | WHERE id > last_id | élevée | stable (O(log n)) |
| Keyset | WHERE key > last_key | élevée | stable (O(log n)) |
| Time-based | WHERE created_at < last_time | moyenne | stable avec index |
La pagination Offset convient aux ensembles de données statiques ou rarement mis à jour, lorsque la simplicité d’implémentation est importante. Cursor et Keyset sont pour les données dynamiques avec des insertions fréquentes. La pagination temporelle est pour les flux chronologiques où les enregistrements sont ordonnés par date de création. La norme GraphQL Relay utilise la pagination par curseur comme seule méthode recommandée.
La pagination Offset est le type le plus simple de chargement par pages. Le client envoie les paramètres page et limit (ou offset et limit), et le serveur applique SQL OFFSET et LIMIT. Par exemple, page=2, limit=20 renvoie les enregistrements 21 à 40. Cette méthode est intuitive et facile à implémenter sur n’importe quelle stack.
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items")
async def get_items(
page: int = Query(default=1, ge=1),
limit: int = Query(default=20, le=100)
):
offset = (page - 1) * limit
items = await fetch_items(offset, limit)
total = await count_items()
return {
"items": items,
"total": total,
"page": page,
"pages": (total + limit - 1) // limit
}
Le principal inconvénient de la pagination Offset est le problème d’enregistrements manqués et dupliqués. Si de nouveaux enregistrements sont ajoutés à la table entre deux requêtes, le OFFSET se décale : l’utilisateur peut voir le même enregistrement deux fois ou en manquer un nouveau. C’est critique pour les flux d’actualités et les chats où la cohérence est importante.
Un autre problème est la dégradation des performances sur les grands OFFSET. La base de données doit analyser et sauter les premiers offset enregistrements avant de renvoyer le résultat. Avec offset=100000 même avec LIMIT 20, le serveur passera un temps notable à analyser. PostgreSQL et MySQL montrent une chute linéaire de la vitesse à mesure que OFFSET croît.
La pagination Offset reste le meilleur choix pour : les panneaux d’administration (les données changent rarement, la navigation par pages est nécessaire), les rapports et les logs historiques (instantané fixe des données), les catalogues avec filtres (on peut aller à n’importe quelle page). Offset est aussi le plus simple à implémenter côté client — RecyclerView avec Paging 3 le supporte nativement.
La pagination Keyset utilise une clé unique (généralement la clé primaire) pour filtrer les enregistrements. Au lieu de OFFSET, la requête utilise WHERE id > last_seen_id. Cela garantit des performances stables indépendamment du nombre d’enregistrements et l’absence de doublons lors des insertions, car les nouveaux enregistrements ont toujours un id plus grand.
-- Pagination Offset (problématique)
SELECT * FROM posts
ORDER BY id
LIMIT 20 OFFSET 100;
-- Pagination Keyset (stable)
SELECT * FROM posts
WHERE id > 100
ORDER BY id
LIMIT 20;
La pagination temporelle (ou curseur temporel) utilise l’horodatage created_at pour la navigation. Le client envoie le timestamp du dernier enregistrement chargé, et le serveur renvoie les enregistrements créés avant ou après ce timestamp. Cette méthode est populaire dans les réseaux sociaux et les flux d’actualités où l’ordre des enregistrements est déterminé par l’heure de publication.
Une particularité de la pagination temporelle est la possibilité de doublons si deux enregistrements sont créés dans la même milliseconde. Pour éliminer ce problème, combinez une clé temporelle avec un id unique : WHERE (created_at, id) < (last_time, last_id). Un tel curseur composite garantit l’unicité de chaque enregistrement et un ordre précis.
La pagination Keyset nécessite une colonne avec une valeur unique et monotone croissante (id auto-incrémenté, UUID v7). La pagination temporelle convient à toute table avec created_at, mais nécessite un traitement supplémentaire des doublons. La différence principale : Keyset fonctionne de manière stable pour toute opération d’insertion, tandis que la temporelle est sensible aux horodatages identiques.
Le choix du type de pagination dépend de la nature des données et des exigences de l’expérience utilisateur. Vous trouverez ci-dessous des recommandations pour les scénarios typiques du développement mobile. Il n’existe pas de solution universelle — chaque méthode a un domaine où elle est optimale.
La bibliothèque Android Paging 3 supporte tous les types de pagination via PagingSource. Pour Offset — PagingSource avec clé Int (page), pour Cursor — avec clé String ou Long (cursor). PagingSource gère automatiquement le chargement, la mise en cache et les tentatives en cas d’erreur.
class PostPagingSource(
private val api: PostApi
) : PagingSource<Long, Post>() {
override suspend fun load(
params: LoadParams<Long>
): LoadResult<Long, Post> {
val cursor = params.key ?: Long.MAX_VALUE
return try {
val response = api.getPosts(cursor, params.loadSize)
LoadResult.Page(
data = response.items,
prevKey = null,
nextKey = response.items.lastOrNull()?.id
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
override fun getRefreshKey(state: PagingState<Long, Post>): Long? {
return state.anchorPosition?.let {
state.closestItemToPosition(it)?.id
}
}
}
La taille de page affecte la vitesse de chargement et la perception des performances. Pour les applications mobiles, la plage optimale est de 10–25 éléments par page. Moins de 10 entraîne trop de requêtes à l’API et un défilement saccadé. Plus de 25 entraîne un chargement lent de la première portion sur les réseaux lents.
Pour les images et les vidéos, réduisez la taille de page à 5–10, car chaque élément nécessite du temps supplémentaire pour charger les médias. Pour les listes textuelles (commentaires, logs), la taille peut être augmentée à 30–50 enregistrements. Il est recommandé de rendre la taille de page configurable via l’API afin que le client puisse s’adapter à différentes conditions réseau.
Questions fréquentes
La pagination consiste à charger les données par portions, pas tout à la fois. Comme dans un livre : vous lisez une page, puis vous tournez à la suivante. Dans une application, cela signifie qu’en faisant défiler une liste, le lot de données suivant se charge, pas la liste entière, ce qui économise le trafic et la mémoire.
Offset compte les enregistrements : « saute 20, renvoie les 10 suivants ». Si un nouvel enregistrement est ajouté entre les chargements, la numérotation se décale. Cursor utilise l’identifiant unique du dernier enregistrement : « renvoie 10 enregistrements après ID = 100 ». Les nouveaux enregistrements n’affectent pas la position.
Pour les applications mobiles, 10–25 éléments est optimal. Pour les listes avec images 5–10, pour les flux textuels 20–30. La taille dépend de la taille moyenne de chaque élément : plus l’élément est lourd, plus la page doit être petite pour un affichage rapide.
Utilisez la bibliothèque Paging 3 d’Android Jetpack. Elle fournit PagingSource pour le chargement, PagingData pour les flux réactifs et PagingDataAdapter pour le chargement automatique au défilement. La bibliothèque supporte la pagination Offset, Cursor et Keyset via un PagingSource personnalisé.
Le défilement infini est un modèle d’interface où un nouveau lot de données se charge automatiquement à l’approche de la fin de la liste. La pagination est le mécanisme de chargement des données par lots, et le défilement infini est une façon de l’afficher. Une alternative est le bouton « Charger plus ».
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi