0Pricing
Android Academy · Lektion

RemoteMediator und Caching

Paging für Netzwerk und Datenbank kombinieren

RemoteMediator und Caching ist eine kostenlose Android Academy-Lektion auf CoddyKit. Dies ist Lektion 4 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des Android Academy-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der Android Academy-Kurs umfasst insgesamt 4 Lektionen.

Offline-First-Paging

Eine reine Netzwerk-PagingSource funktioniert, aber wenn die Verbindung abbricht, ist die Liste leer. Für eine robuste App sollte die Datenbank die einzige Quelle der Wahrheit sein und im Hintergrund vom Netzwerk befüllt werden.

Genau das ermöglicht RemoteMediator: Die Seiten werden aus der lokalen Datenbank geladen, und weitere Daten werden aus dem Netzwerk abgerufen, sobald die Datenbank keine mehr enthält.

Die Architektur

Mit RemoteMediator fließen die Daten in eine Richtung:

  • Die UI lädt Seiten aus einer Room-PagingSource
  • Wenn Room das Ende erreicht, ruft RemoteMediator die nächste Netzwerkseite ab
  • Das Netzwerkergebnis wird in Room geschrieben
  • Room gibt die neuen Zeilen aus und die UI wird aktualisiert

Die Benutzer sehen zwischengespeicherte Daten sofort, auch offline.

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

Room-PagingSource ohne Zusatzaufwand

Room kann automatisch eine PagingSource generieren. Deklarieren Sie einfach eine Abfrage, die PagingSource<Int, Entity> zurückgibt, und Room implementiert sie.

Aus dieser Quelle lädt die UI tatsächlich ihre Seiten.

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 ableiten

RemoteMediator<Key, Value> verfügt über eine erforderliche Methode, load(), sowie eine optionale Methode, initialize().

load() erhält einen LoadType (REFRESH, PREPEND oder APPEND) und den aktuellen PagingState und gibt anschließend ein MediatorResult zurück.

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 verarbeiten

In load() entscheiden Sie anhand des LoadType, welche Seite abgerufen werden soll:

  • REFRESH – die erste Seite laden (oder den Bereich um den Anker)
  • PREPEND – normalerweise nichts zu tun; endOfPaginationReached = true zurückgeben
  • APPEND – die Seite nach der letzten vorhandenen Seite laden
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
    }
}

Tabelle für Remote Keys

Anders als eine PagingSource kann ein RemoteMediator die Schlüssel für die nächste bzw. vorherige Seite nicht direkt zurückgeben – sie müssen dauerhaft gespeichert werden. Speichern Sie sie zusammen mit Ihren Daten in einer kleinen Tabelle für remote keys.

So können Sie APPEND/PREPEND nach einem Neustart der App fortsetzen.

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

Netzwerkergebnisse in Room schreiben

Schreiben Sie die Elemente und ihre Schlüssel nach dem Abrufen einer Seite innerhalb einer Transaktion in Room. Leeren Sie bei REFRESH zuerst die alten Tabellen, damit der Cache konsistent bleibt.

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

MediatiorResult zurückgeben

Beenden Sie load(), indem Sie ein MediatorResult zurückgeben:

  • Success(endOfPaginationReached = true), wenn keine weiteren Seiten vorhanden sind
  • Success(endOfPaginationReached = false), wenn weitere Seiten vorhanden sein könnten
  • Error(e) bei einem Fehler – dieser wird als LoadState.Error weitergegeben
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 mit remoteMediator verbinden

Erstellen Sie den Pager sowohl mit dem remoteMediator als auch mit der Room-pagingSourceFactory. Der Mediator befüllt Room; die UI lädt Seiten aus Room.

Da hierfür eine experimentelle API verwendet wird, müssen Sie sie mit @OptIn(ExperimentalPagingApi::class) aktivieren.

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

Schlüssel für APPEND nachschlagen

Für einen APPEND-Ladevorgang benötigen Sie den nextKey des zuletzt geladenen Elements. Verwenden Sie den Anker im PagingState, um den passenden Remote Key aus Ihrer Schlüsseltabelle zu ermitteln.

Hilfsfunktionen sorgen dafür, dass load() für REFRESH, APPEND und PREPEND übersichtlich bleibt.

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 und Aktualität

Da die Daten in Room gespeichert werden, lädt die App sofort und funktioniert offline. Um veraltete Inhalte zu vermeiden, überschreiben Sie initialize(), um anhand des Alters des Caches zu entscheiden, ob beim Start aktualisiert werden soll.

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

Kurze Überprüfung

Welche Komponente ist in einer RemoteMediator-Konfiguration die einzige Quelle der Wahrheit, aus der die UI ihre Seiten lädt?

Zusammenfassung: RemoteMediator und Caching

Sie haben eine Offline-First-Paging-Pipeline erstellt:

  • Die UI lädt Seiten aus einer Room-PagingSource – der einzigen Quelle der Wahrheit
  • RemoteMediator.load() verarbeitet REFRESH/PREPEND/APPEND und schreibt die Ergebnisse innerhalb einer Transaktion in Room
  • Eine Tabelle für remote keys speichert die Schlüssel für die nächste bzw. vorherige Seite über Neustarts hinweg
  • Der Pager erhält sowohl einen remoteMediator als auch eine pagingSourceFactory
  • initialize() steuert die Aktualität des Caches

Sie können nun endlose Listen online oder offline effizient laden. Herzlichen Glückwunsch zum Abschluss des Kurses zu Paging 3.

Häufig gestellte Fragen

Ist die Lektion „RemoteMediator und Caching“ kostenlos?

Ja — der vollständige Text von „RemoteMediator und Caching“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des Android Academy-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der Android Academy-Kurs umfasst insgesamt 4 Lektionen.

Was lerne ich in „RemoteMediator und Caching“?

Paging für Netzwerk und Datenbank kombinieren Du übst Android Academy mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.

Brauche ich Erfahrung, um Android Academy zu starten?

Keine Vorkenntnisse erforderlich. Android Academy auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 4 von 4.

Wie lange dauert die Lektion „RemoteMediator und Caching“?

Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.

Kann ich in dieser Android Academy-Lektion Code schreiben und ausführen?

Ja. Jede Android Academy-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.

Alle Lektionen in diesem Kurs

  1. Warum Paging?
  2. PagingSource und Pager
  3. Paging in Compose-Listen
  4. RemoteMediator und Caching
← Zurück zu Android Academy