Android Academy · leksjon

RemoteMediator og hurtigbufring

Kombiner nettverks- og databasebasert paginering.

Leksjon 4 av 413 trinn

RemoteMediator og hurtigbufring er en gratis leksjon i Android Academy på CoddyKit. Dette er leksjon 4 av 4. Du kan lese valgfritt 3 leksjoner fra denne læringsstien gratis i sin helhet – deretter låser CoddyKit PRO opp alle leksjoner, samt praktisk øving med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Den er en del av læringsløpet i Android Academy, og fremdriften din synkroniseres mellom nettet og CoddyKit-appen. Kurset i Android Academy inneholder totalt 4 leksjoner.

Offline-først-paginering

En vanlig nettverksbasert PagingSource fungerer, men hvis forbindelsen brytes, blir listen tom. For en robust app ønsker De databasen som den eneste kilden til sannhet, mens nettverket fyller den ut i bakgrunnen.

Det er nettopp dette RemoteMediator muliggjør: hent sider fra den lokale databasen, og hent mer fra nettverket når databasen går tom.

Arkitekturen

Med RemoteMediator flyter dataene i én retning:

  • Grensesnittet henter sider fra en Room PagingSource
  • Når Room nærmer seg slutten, henter RemoteMediator neste nettverksside
  • Nettverksresultatet skrives til Room
  • Room sender ut de nye radene, og grensesnittet oppdateres

Brukeren ser alltid bufrede data umiddelbart, selv uten nett.

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

Room PagingSource helt gratis

Room kan generere en PagingSource automatisk. De trenger bare å deklarere en spørring som returnerer PagingSource<Int, Entity>, så implementerer Room den.

Dette er kilden grensesnittet faktisk henter sider fra.

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

Arve fra RemoteMediator

RemoteMediator<Key, Value> har én påkrevd metode, load(), samt en valgfri initialize().

load() mottar en LoadType (REFRESH, PREPEND eller APPEND) og gjeldende PagingState, og returnerer deretter en 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
}

Håndtere LoadType

I load() avgjør De hvilken side som skal hentes, basert på LoadType:

  • REFRESH – last inn den første siden (eller rundt ankeret)
  • PREPEND – vanligvis er det ingenting å gjøre; returner endOfPaginationReached = true
  • APPEND – last inn siden etter den siste siden vi har
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
    }
}

Tabell for eksterne nøkler

I motsetning til en PagingSource kan ikke en RemoteMediator returnere neste-/forrige-nøkler direkte – de må lagres. Lagre dem i en liten tabell med eksterne nøkler ved siden av dataene.

Da kan De fortsette med APPEND/PREPEND etter at appen har blitt startet på nytt.

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

Skrive nettverksresultater til Room

Etter at en side er hentet, skriver De elementene og nøklene deres til Room i en transaksjon. Ved REFRESH må De først tømme de gamle tabellene, slik at hurtigbufferen forblir konsistent.

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

Returnere MediatorResult

Avslutt load() ved å returnere en MediatorResult:

  • Success(endOfPaginationReached = true) når det ikke finnes flere sider
  • Success(endOfPaginationReached = false) når det kan finnes flere
  • Error(e) ved feil – vises som en 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)
}

Koble Pager til remoteMediator

Bygg Pager med både remoteMediator og Room-pagingSourceFactory. Mediatoren fyller Room; grensesnittet henter sider fra Room.

Ettersom dette bruker et eksperimentelt API, må De aktivere det med @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

Slå opp nøkler for APPEND

Ved en APPEND-lasting trenger De nextKey for det sist innlastede elementet. Bruk ankeret i PagingState til å finne riktig ekstern nøkkel i nøkkeltabellen.

Hjelpefunksjoner gjør load() mer oversiktlig for REFRESH, APPEND og 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)

Bufring og aktualitet

Ettersom dataene ligger i Room, lastes appen inn umiddelbart og fungerer uten nett. For å unngå utdatert innhold kan De overstyre initialize() og avgjøre om det skal oppdateres ved oppstart, basert på hvor gammelt hurtigbufferet er.

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

Rask kontroll

I et RemoteMediator-oppsett, hva er den eneste kilden til sannhet som grensesnittet henter sider fra?

Oppsummering: RemoteMediator og bufring

De har bygget en offline-først-pipeline for paginering:

  • Grensesnittet henter sider fra en Room PagingSource – den eneste kilden til sannhet
  • RemoteMediator.load() håndterer REFRESH/PREPEND/APPEND og skriver resultatene til Room i en transaksjon
  • En tabell med eksterne nøkler lagrer neste-/forrige-nøkler mellom omstarter
  • Pager tar både en remoteMediator og en pagingSourceFactory
  • initialize() styrer hvor aktuelt hurtigbufferet er

De kan nå laste inn endeløse lister effektivt, både med og uten nett. Gratulerer med å ha fullført Paging 3-kurset.

Gratis å komme i gang

Lær deg Kotlin med en AI-veileder – gratis

Skriv og kjør ekte kode i nettleseren, få umiddelbar hjelp fra en AI-veileder som er tilgjengelig døgnet rundt, og fortsett der du slapp – på nettet eller i appen.

Kurs
36
Leksjoner
152

Ofte stilte spørsmål

Er leksjonen «RemoteMediator og hurtigbufring» gratis?

Ja – du kan lese valgfritt 3 av leksjonene i læringsstien Android Academy, inkludert «RemoteMediator og hurtigbufring», gratis i sin helhet her på nettet. Deretter låser CoddyKit PRO opp alle leksjoner, samt interaktiv øving med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Kurset i Android Academy inneholder totalt 4 leksjoner.

Hva lærer jeg i «RemoteMediator og hurtigbufring»?

Kombiner nettverks- og databasebasert paginering. Du øver på Android Academy med praktisk kode som du kjører direkte i nettleseren, mens en AI-veileder som er tilgjengelig døgnet rundt, svarer på spørsmålene dine mens du jobber deg gjennom leksjonen.

Trenger jeg erfaring for å begynne med Android Academy?

Ingen tidligere erfaring er nødvendig. Android Academy på CoddyKit er lagt opp for både nybegynnere og viderekomne, så De kan begynne her eller helt fra start og lære i Deres eget tempo. Dette er leksjon 4 av 4.

Hvor lang tid tar leksjonen «RemoteMediator og hurtigbufring»?

De fleste CoddyKit-leksjoner tar omtrent 5–10 minutter. Hver leksjon er kort og interaktiv, slik at De gjør jevne fremskritt og kan fortsette akkurat der De slapp – både på nettet og i appen.

Kan jeg skrive og kjøre kode i denne Android Academy-leksjonen?

Ja. Alle Android Academy-leksjoner har en innebygd kodeeditor, slik at De kan skrive og kjøre ekte kode direkte i nettleseren og få umiddelbar tilbakemelding fra AI – uten lokal konfigurering.

Alle leksjonene i dette kurset

  1. Hvorfor Paging
  2. PagingSource og Pager
  3. Paging i Compose-lister
  4. RemoteMediator og hurtigbufring
← Tilbake til Android Academy