In an offline-first app the local database is the single source of truth: the UI observes only Room 234 , the network only updates Room, and the screen follows through the Flow of Reactive Queries. Nothing waits for the network before drawing:

Two small changes complete the pattern. The repository exposes Room's Flow and a status built from the SyncState DataStore 234 of Proto DataStore, and the ViewModel shows the cache first, then refreshes without dropping back to a spinner:
viewModelScope.launch { // 5.27.8 cached rows first...
repo.observeBooks().collect { if (it.isNotEmpty()) load.value = LoadState.Success(it) }
}
refresh() // ...then ask the network
...
if (load.value !is LoadState.Success) load.value = LoadState.Loading // keep the cacheThe interface gives observeBooks() and observeStatus() defaults (emptyFlow()), so Testing with Fake Dependencies's fake and tests still passed. With the test server stopped, a cold start logged:
I/OkHttp : <-- HTTP FAILED: java.net.ConnectException: Failed to connect to /10.0.2.2:8616. http://10.0.2.2:8616/books (490ms) W/BookNestNet: offline: java.net.ConnectException: Failed to connect to /10.0.2.2:8616 D/BookNestVM: loaded: Success

React Native's TanStack Query 79,069 cache (useQuery and Caching) approximates the same idea in JavaScript. Still missing: refreshing on a schedule (WorkManager 234 , Background Sync), and offline writes, which need a queue of pending changes and a conflict rule such as "last write wins".