Offline-First Repository
Single Source of Truth & NetworkBoundResource
An offline-first app treats the local database as the single source of truth. The UI always reads from Room; the network only *updates* that cache. The screen works with no connection and refreshes when one returns.
๐ Explanation
The "single source of truth" rule says the UI never reads the network directly โ it reads the database, and the network's only job is to keep that database fresh. The `NetworkBoundResource` pattern modeled here emits the cached data immediately (instant render, even offline), attempts a refresh, writes the result back to Room with `upsertAll`, then re-emits from the cache so the UI always observes one consistent shape. Wrapping results in a `Resource` sealed type lets the screen distinguish loading-with-stale-data, success, and error-but-still-show-cache, which is exactly the resilient behavior users expect on flaky connections.
Note: Code execution is not available for Android/Kotlin in the browser. Use Android Studio to run and test the app.
import kotlinx.coroutines.flow.Flowimport kotlinx.coroutines.flow.flowimport kotlinx.coroutines.flow.flowOnimport kotlinx.coroutines.Dispatcherssealed interface Resource<out T> {data class Loading<T>(val data: T?) : Resource<T>data class Success<T>(val data: T) : Resource<T>data class Error<T>(val message: String, val data: T?) : Resource<T>}class ArticleRepository(private val dao: ArticleDao,private val api: ArticleApi) {// Emits cached data first, then refreshes from the network,// and re-emits the updated cache โ the DB stays the source of truth.fun observeArticles(): Flow<Resource<List<Article>>> = flow {val cached = dao.getAll()emit(Resource.Loading(cached))try {val fresh = api.getArticles() // 1. fetchdao.upsertAll(fresh) // 2. write to the SSOT} catch (e: Exception) {emit(Resource.Error(e.message ?: "Network error", cached))return@flow}// 3. Read back from the cache so UI sees a single shapeemit(Resource.Success(dao.getAll()))}.flowOn(Dispatchers.IO)}