Design: Offline-First Feed Quiz

ADVANCED › System Design

In an offline-first feed, where should the UI layer read its data from?

Answer: From the local database, which is the single source of truth

The local DB is the canonical source of truth; the UI observes it via Flow and writes update it first, so reads stay consistent and work offline.

What is the primary role of RemoteMediator in Paging 3?

Answer: It loads network pages and stores them in the local DB

RemoteMediator bridges network and DB: when the DB-backed PagingSource is exhausted it loads more from the network and persists it, keeping the DB authoritative.

When RemoteMediator.load() is called with LoadType.REFRESH, what is the typical correct behavior?

Answer: Clear cached items and keys, then insert the first page atomically

REFRESH means reload from the top, so you atomically clear cached items and keys and insert the new first page to avoid duplicates and stale keys.

Last-write-wins conflict resolution relies on what mechanism?

Answer: Timestamp or version metadata so older writes get discarded

Devices attach a timestamp/version; the authoritative source keeps newer writes and discards anything older than its current state.

Why prefer WorkManager over a plain ViewModel coroutine for background sync?

Answer: It survives process death and reboots, and waits for connectivity

Sync must survive the app being killed and should wait for network; WorkManager provides durable, constraint-aware, deduplicated execution that a ViewModel coroutine cannot.

A CoroutineWorker doing sync returns Result.retry(). What happens?

Answer: WorkManager reschedules it later with exponential backoff by default

Result.retry() tells WorkManager to run the work again later, increasing the delay with exponential backoff (configurable) so transient failures recover gracefully.

The feed shows cached items, but loading the next page from the network fails. What is the best Paging 3 UX?

Answer: Keep the cached list and show a retry footer on append LoadState.Error

Cached content should remain visible; an append error is local to the bottom of the list, so a footer with a retry action is the correct, non-destructive response.

What does the RemoteKeys table store and why is it needed?

Answer: Maps items or pages to prev/next tokens so RemoteMediator knows what to fetch

RemoteMediator reads the RemoteKeys table to find the next or previous page token for the item at the edge of the loaded data, since the page tokens are not part of the displayed entities themselves.

After a background sync writes new rows into Room, why does the feed UI update without any manual refresh call?

Answer: Room returns Flow queries that re-emit when tables change

A DAO query returning Flow is reactive: Room re-runs it and emits a new list whenever the queried tables are modified, so consumers collecting the Flow get the fresh data automatically.

For a 'like' button that must feel instant even with no connection, which write strategy is most appropriate?

Answer: Local-first: write to the DB now, then sync later with conflicts

Local-first writes update Room right away so the UI reflects the change instantly offline, deferring the network sync; because two devices can diverge, it requires a conflict-resolution policy.

How does RemoteMediator.load() signal that there are no more pages to fetch from the network?

Answer: It returns MediatorResult.Success(endOfPaginationReached = true)

Returning MediatorResult.Success(endOfPaginationReached = true) tells Paging the network has been fully consumed, so it stops invoking the mediator for further APPEND/PREPEND loads.

Which approach correctly prevents duplicate periodic sync jobs from stacking up each time the app launches?

Answer: Use enqueueUniquePeriodicWork with a stable name and KEEP

enqueueUniquePeriodicWork with a unique name and KEEP ensures only one periodic chain exists for that name, so repeated scheduling calls do not create duplicate workers.

Using Paging 3 LoadState, how do you detect a genuine empty state (no items at all) versus still loading?

Answer: refresh is NotLoading, endOfPaginationReached true, and itemCount is 0

An empty result means the refresh finished (NotLoading), pagination is exhausted (endOfPaginationReached), and there is genuinely nothing to show (itemCount 0); refresh Loading with 0 items is just the initial fetch in progress.

What is the most effective way to unit-test a RemoteMediator's load() logic?

Answer: Call load() on an in-memory Room DB and fake API, asserting rows and keys

Calling load() against an in-memory Room DB with a fake network lets you deterministically assert that the correct entities and remote keys are persisted and that the right MediatorResult is returned for REFRESH/APPEND/PREPEND.

Back to Design: Offline-First Feed