Android Academy · Les

RemoteMediator en caching

Combineer paging van netwerk en database.

Les 4 van 413 stappen

RemoteMediator en caching is een gratis Android Academy-les op CoddyKit. Dit is les 4 van 4. Je kunt 3 lessen uit dit leerpad gratis volledig lezen — daarna ontgrendelt CoddyKit PRO alle lessen, plus praktische oefeningen met een ingebouwde code-editor en een AI-tutor die 24/7 beschikbaar is. Deze les maakt deel uit van het leertraject Android Academy. Je voortgang wordt gesynchroniseerd op het web en in de CoddyKit-app. De cursus Android Academy bevat in totaal 4 lessen.

Offline-first paginering

Een gewone netwerk-PagingSource werkt, maar als de verbinding wegvalt, is de lijst leeg. Voor een robuuste app wil je de database als enige bron van waarheid gebruiken en het netwerk de database op de achtergrond laten vullen.

Dat is precies wat RemoteMediator mogelijk maakt: pagina's laden vanuit de lokale database en meer gegevens ophalen van het netwerk wanneer de database geen gegevens meer bevat.

De architectuur

Met RemoteMediator stromen de gegevens in één richting:

  • De gebruikersinterface laadt pagina's vanuit een Room-PagingSource
  • Wanneer Room het einde nadert, haalt RemoteMediator de volgende netwerkpagina op
  • Het netwerkresultaat wordt naar Room geschreven
  • Room geeft de nieuwe rijen door en de gebruikersinterface wordt bijgewerkt

De gebruiker ziet altijd onmiddellijk gecachte gegevens, zelfs offline.

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

Room PagingSource cadeau

Room kan automatisch een PagingSource genereren. Declareer gewoon een query die PagingSource<Int, Entity> retourneert; Room implementeert deze dan.

Dit is de bron waaruit de gebruikersinterface daadwerkelijk pagina's laadt.

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>
}

RemoteMediator subklassen

RemoteMediator<Key, Value> heeft één verplichte methode, load(), en daarnaast een optionele initialize().

load() ontvangt een LoadType (REFRESH, PREPEND of APPEND) en de huidige PagingState, en retourneert vervolgens een 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
}

LoadType verwerken

In load() bepaal je op basis van het LoadType welke pagina je ophaalt:

  • REFRESH - laad de eerste pagina (of de pagina rond het anker)
  • PREPEND - meestal hoef je niets te doen; retourneer endOfPaginationReached = true
  • APPEND - laad de pagina na de laatste pagina die we hebben
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
    }
}

Tabel met externe sleutels

In tegenstelling tot een PagingSource kan een RemoteMediator volgende/vorige sleutels niet rechtstreeks retourneren: ze moeten worden bewaard. Sla ze op in een kleine tabel met externe sleutels, naast je gegevens.

Zo kun je APPEND/PREPEND na het opnieuw starten van de app hervatten.

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?
)

Netwerkresultaten naar Room schrijven

Schrijf de items en hun sleutels na het ophalen van een pagina binnen een transactie naar Room. Wis bij REFRESH eerst de oude tabellen, zodat de cache consistent blijft.

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() })
}

MediatResult retourneren

Sluit load() af door een MediatorResult te retourneren:

  • Success(endOfPaginationReached = true) wanneer er geen pagina's meer zijn
  • Success(endOfPaginationReached = false) wanneer er mogelijk meer pagina's zijn
  • Error(e) bij een fout - deze wordt doorgegeven als een 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)
}

Pager verbinden met remoteMediator

Bouw de Pager met zowel de remoteMediator als de Room-pagingSourceFactory. De mediator vult Room; de gebruikersinterface laadt pagina's vanuit Room.

Omdat hiervoor een experimentele API wordt gebruikt, meld je je aan met @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

Sleutels opzoeken voor APPEND

Voor een APPEND-lading heb je de nextKey van het laatst geladen item nodig. Gebruik het PagingState-anker om de juiste externe sleutel in je sleutelstabel te vinden.

Hulpfuncties houden load() overzichtelijk voor REFRESH, APPEND en 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)

Caching en actualiteit

Omdat de gegevens in Room staan, laadt de app onmiddellijk en werkt deze offline. Om verouderde inhoud te voorkomen, overschrijf je initialize() om op basis van de leeftijd van de cache te bepalen of de gegevens bij het starten moeten worden vernieuwd.

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
    }
}

Korte controle

Wat is in een RemoteMediator-opstelling de enige bron van waarheid waaruit de gebruikersinterface pagina's laadt?

Samenvatting: RemoteMediator en caching

Je hebt een offline-first-pagineerpijplijn gebouwd:

  • De gebruikersinterface laadt pagina's vanuit een Room-PagingSource - de enige bron van waarheid
  • RemoteMediator.load() verwerkt REFRESH/PREPEND/APPEND en schrijft de resultaten binnen een transactie naar Room
  • Een tabel met externe sleutels bewaart volgende/vorige sleutels na opnieuw starten
  • De Pager ontvangt zowel een remoteMediator als een pagingSourceFactory
  • initialize() bepaalt hoe actueel de cache is

Je kunt nu eindeloze lijsten efficiënt laden, online of offline. Gefeliciteerd met het voltooien van de cursus Paging 3.

Gratis beginnen

Leer Kotlin met een AI-tutor — gratis

Schrijf echte code en voer die uit in je browser, krijg direct hulp van een AI-tutor die 24/7 beschikbaar is en ga verder waar je gebleven bent op het web of in de app.

Cursussen
36
Lessen
152

Veelgestelde vragen

Is de les “RemoteMediator en caching” gratis?

Ja — je kunt hier op het web alle 3 lessen van het leerpad Android Academy, waaronder “RemoteMediator en caching”, gratis volledig lezen. Daarna ontgrendelt CoddyKit PRO alle lessen, plus interactieve oefeningen met een ingebouwde code-editor en een AI-tutor die 24/7 beschikbaar is. De cursus Android Academy bevat in totaal 4 lessen.

Wat leer ik in “RemoteMediator en caching”?

Combineer paging van netwerk en database. Je oefent met Android Academy door code rechtstreeks in de browser uit te voeren. Een AI-begeleider die 24/7 beschikbaar is beantwoordt je vragen terwijl je de les doorwerkt.

Heb ik ervaring nodig om met Android Academy te beginnen?

Ervaring vooraf is niet nodig. Android Academy op CoddyKit is opgebouwd voor beginners tot gevorderden, zodat je hier of bij het begin kunt starten en in je eigen tempo kunt leren. Dit is les 4 van 4.

Hoe lang duurt de les “RemoteMediator en caching”?

De meeste lessen van CoddyKit duren ongeveer 5–10 minuten. Elke les is kort en interactief, zodat je gestaag vooruitgaat en op het web en in de app precies verdergaat waar je was gebleven.

Kan ik code schrijven en uitvoeren in deze les over Android Academy?

Ja. Elke les over Android Academy bevat een ingebouwde code-editor, zodat je rechtstreeks in je browser echte code kunt schrijven en uitvoeren en direct feedback van AI krijgt — lokale installatie is niet nodig.

Alle lessen in deze cursus

  1. Waarom paging
  2. PagingSource en Pager
  3. Paging in Compose-lijsten
  4. RemoteMediator en caching
← Terug naar Android Academy