RemoteMediator and database-backed paging
RemoteMediator fetches network pages and stores them in DB. Room PagingSource remains UI read path.
Pager(
config = PagingConfig(pageSize = 30),
remoteMediator = ProductsRemoteMediator(query, db, api),
pagingSourceFactory = { db.productDao().pagingSource(query) },
).flow
Load types
- REFRESH: initial/manual/new generation around current position
- APPEND: need data after end
- PREPEND: need before beginning; many feeds return end immediately
Remote key table
Keys must be scoped by query/filter/account/sort/version. One global nextCursor causes categories/users to consume each other's pagination.
remote_keys(scope_key PRIMARY KEY, next_cursor, updated_at, end_reached)
Atomic page write
db.withTransaction {
if (loadType == LoadType.REFRESH) {
productDao.deleteForScope(scope)
keyDao.delete(scope)
}
productDao.upsertAll(response.items.map { it.toEntity(scope) })
keyDao.upsert(RemoteKey(scope, response.nextCursor))
}
Data and key commit together. Crash cannot leave cursor advanced without rows. However, clearing on refresh can erase useful offline data or pending local edits—only clear server-owned scoped cache and reconcile local pending state.
initialize()
Return SKIP_INITIAL_REFRESH when cache freshness policy says usable; otherwise LAUNCH_INITIAL_REFRESH, which blocks boundary loads until refresh succeeds. Device wall clock/TTL caveats apply.
Duplicate requests
Paging coordinates loads per generation, but duplicate Pagers/collectors, wrong key persistence, invalidation loops, or repeated recreation can still duplicate network work. Create Pager once in repository/ViewModel path and cachedIn correct scope.
Transaction and network
Network call outside DB transaction. Fetch then transact response; use server versions to prevent older response overwriting newer refresh/mutation. A Mutex is not durable/version proof.
Empty/end
Empty page may be temporary filtering/server issue; follow explicit nextCursor/end flag. Returning endOfPaginationReached=true permanently stops boundary load for that generation.
RemoteMediator is a synchronization trigger. Correctness still depends on scoped remote keys, stable server ordering, atomic DB/key writes, versioning, and refresh deletion policy.
Sources
Exercises
- Scope keys for account + query + sort.
- Refresh without deleting pending favorites.
- Diagnose cursor advanced but page rows missing.