In an offline-first design, the database is the observable source of truth. Network responses update Room, and the UI observes Room through Flow. This avoids competing local and remote states.
Practical guidance
- Render DAO flows instead of returning the latest network body directly.
- Wrap related writes in transactions.
- Track sync metadata such as server version or updatedAt.
- Schedule durable sync with WorkManager under appropriate constraints.
Working example
@Dao
interface ArticleDao {
@Query("SELECT * FROM articles ORDER BY updatedAt DESC")
fun observeAll(): Flow<List<ArticleEntity>>
@Upsert
suspend fun upsertAll(items: List<ArticleEntity>)
}
suspend fun refresh() { dao.upsertAll(api.articles().map { it.toEntity() }) }
Common mistakes
- Deleting all rows before inserting creates empty-state flicker unless transactional.
- Last-write-wins is a policy, not a universal conflict solution.
- Database entities should not leak into Compose screens.
Key takeaway
Make synchronization an explicit data-layer responsibility. The UI should observe one source and remain usable when the network disappears.
