0Pricing
Android Academy · Leçon

RemoteMediator et mise en cache

Combinez la pagination réseau et celle de la base de données.

RemoteMediator et mise en cache est une leçon Android Academy gratuite sur CoddyKit. Ceci est la leçon 4 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.

Pagination hors ligne en priorité

Un PagingSource réseau classique fonctionne, mais si la connexion est interrompue, la liste est vide. Pour une application robuste, vous devez utiliser la base de données comme source unique de vérité et laisser le réseau la remplir en arrière-plan.

C'est exactement ce que permet RemoteMediator : paginer depuis la base de données locale et récupérer davantage de données sur le réseau lorsque la base de données est épuisée.

L'architecture

Avec RemoteMediator, les données circulent dans une seule direction :

  • L'interface utilisateur effectue la pagination depuis un PagingSource de Room
  • Lorsque Room approche de la fin, RemoteMediator récupère la page réseau suivante
  • Le résultat du réseau est écrit dans Room
  • Room émet les nouvelles lignes et l'interface utilisateur se met à jour

L'utilisateur voit toujours les données mises en cache instantanément, même hors ligne.

// Network ---> RemoteMediator ---> Room (source of truth) ---> UI
// UI never reads the network directly

PagingSource Room sans effort

Room peut générer automatiquement un PagingSource. Il vous suffit de déclarer une requête qui renvoie PagingSource<Int, Entity> et Room l'implémente.

C'est cette source depuis laquelle l'interface utilisateur effectue réellement la pagination.

import androidx.paging.PagingSource
import androidx.room.Dao
import androidx.room.Query

@Dao
interface ArticleDao {
    @Query("SELECT * FROM articles ORDER BY position ASC")
    fun pagingSource(): PagingSource<Int, ArticleEntity>
}

Sous-classer RemoteMediator

RemoteMediator<Key, Value> possède une méthode obligatoire, load(), ainsi qu'une méthode facultative, initialize().

load() reçoit un LoadType (REFRESH, PREPEND ou APPEND) et le PagingState actuel, puis renvoie un MediatorResult.

import androidx.paging.ExperimentalPagingApi
import androidx.paging.RemoteMediator

@OptIn(ExperimentalPagingApi::class)
class ArticleRemoteMediator(
    private val api: ArticleApi,
    private val db: AppDatabase
) : RemoteMediator<Int, ArticleEntity>() {
    // override suspend fun load(loadType, state): MediatorResult
}

Gérer LoadType

Dans load(), vous décidez de la page à récupérer en fonction de LoadType :

  • REFRESH - charger la première page (ou la page située autour de l'ancre)
  • PREPEND - généralement, rien à faire ; renvoyer endOfPaginationReached = true
  • APPEND - charger la page qui suit la dernière page disponible
val page: Int = when (loadType) {
    LoadType.REFRESH -> 1
    LoadType.PREPEND ->
        return MediatorResult.Success(endOfPaginationReached = true)
    LoadType.APPEND -> {
        val lastKey = db.remoteKeyDao().last()?.nextKey
            ?: return MediatorResult.Success(endOfPaginationReached = true)
        lastKey
    }
}

Table des clés distantes

Contrairement à un PagingSource, un RemoteMediator ne peut pas renvoyer directement les clés suivante et précédente : celles-ci doivent être conservées. Stockez-les dans une petite table de clés distantes à côté de vos données.

Cela vous permet de reprendre les opérations APPEND/PREPEND après un redémarrage de l'application.

import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "remote_keys")
data class RemoteKey(
    @PrimaryKey val articleId: Int,
    val prevKey: Int?,
    val nextKey: Int?
)

Écrire les résultats réseau dans Room

Après avoir récupéré une page, écrivez les éléments et leurs clés dans Room au sein d'une transaction. Lors d'un REFRESH, effacez d'abord les anciennes tables afin que le cache reste cohérent.

db.withTransaction {
    if (loadType == LoadType.REFRESH) {
        db.remoteKeyDao().clearAll()
        db.articleDao().clearAll()
    }
    val nextKey = if (response.items.isEmpty()) null else page + 1
    db.remoteKeyDao().insertAll(
        response.items.map { RemoteKey(it.id, prevKey = page - 1, nextKey = nextKey) }
    )
    db.articleDao().insertAll(response.items.map { it.toEntity() })
}

Renvoyer MediatorResult

Terminez load() en renvoyant un MediatorResult :

  • Success(endOfPaginationReached = true) lorsqu'il n'y a plus de pages
  • Success(endOfPaginationReached = false) lorsqu'il peut rester d'autres pages
  • Error(e) en cas d'échec - exposée sous la forme d'un LoadState.Error
return try {
    val response = api.getArticles(page = page, size = state.config.pageSize)
    db.withTransaction { /* write items + keys */ }
    MediatorResult.Success(
        endOfPaginationReached = response.items.isEmpty()
    )
} catch (e: IOException) {
    MediatorResult.Error(e)
} catch (e: HttpException) {
    MediatorResult.Error(e)
}

Connecter Pager à remoteMediator

Construisez le Pager avec à la fois remoteMediator et le pagingSourceFactory de Room. Le médiateur remplit Room ; l'interface utilisateur effectue la pagination depuis Room.

Comme cette fonctionnalité utilise une API expérimentale, activez-la avec @OptIn(ExperimentalPagingApi::class).

@OptIn(ExperimentalPagingApi::class)
fun articles(): Flow<PagingData<ArticleEntity>> = Pager(
    config = PagingConfig(pageSize = 20),
    remoteMediator = ArticleRemoteMediator(api, db),
    pagingSourceFactory = { db.articleDao().pagingSource() }
).flow

Rechercher les clés pour APPEND

Pour un chargement APPEND, vous avez besoin de la nextKey du dernier élément chargé. Utilisez l'ancre du PagingState pour trouver la clé distante correspondante dans votre table de clés.

Des fonctions auxiliaires permettent de garder load() lisible pour REFRESH, APPEND et PREPEND.

private suspend fun lastRemoteKey(
    state: PagingState<Int, ArticleEntity>
): RemoteKey? {
    return state.pages.lastOrNull { it.data.isNotEmpty() }
        ?.data?.lastOrNull()
        ?.let { db.remoteKeyDao().keyFor(it.id) }
}

// In load(): for APPEND
val nextKey = lastRemoteKey(state)?.nextKey
    ?: return MediatorResult.Success(endOfPaginationReached = true)

Mise en cache et actualité des données

Comme les données résident dans Room, l'application se charge instantanément et fonctionne hors ligne. Pour éviter les contenus obsolètes, redéfinissez initialize() afin de déterminer s'il faut actualiser les données au lancement, selon l'ancienneté du cache.

override suspend fun initialize(): InitializeAction {
    val lastUpdate = db.remoteKeyDao().lastUpdatedMillis() ?: 0L
    val cacheTimeout = TimeUnit.HOURS.toMillis(1)
    return if (System.currentTimeMillis() - lastUpdate >= cacheTimeout) {
        InitializeAction.LAUNCH_INITIAL_REFRESH
    } else {
        InitializeAction.SKIP_INITIAL_REFRESH
    }
}

Vérification rapide

Dans une configuration avec RemoteMediator, quelle est la source unique de vérité depuis laquelle l'interface utilisateur effectue la pagination ?

Récapitulatif : RemoteMediator et mise en cache

Vous avez créé une chaîne de traitement de pagination hors ligne en priorité :

  • L'interface utilisateur effectue la pagination depuis un PagingSource de Room, la source unique de vérité
  • RemoteMediator.load() gère REFRESH/PREPEND/APPEND et écrit les résultats dans Room au sein d'une transaction
  • Une table de clés distantes conserve les clés suivante et précédente entre les redémarrages
  • Le Pager reçoit à la fois un remoteMediator et un pagingSourceFactory
  • initialize() contrôle l'actualité du cache

Vous pouvez maintenant charger efficacement des listes infinies, en ligne ou hors ligne. Félicitations pour avoir terminé le cours sur Paging 3.

Questions Fréquemment Posées

La leçon « RemoteMediator et mise en cache » est-elle gratuite ?

Oui — le texte complet de « RemoteMediator et mise en cache » 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 « RemoteMediator et mise en cache » ?

Combinez la pagination réseau et celle de la base de données. 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 4 sur 4.

Combien de temps prend la leçon « RemoteMediator et mise en cache » ?

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