The repository of BookNest DI gets a second implementation, and one line in DataModule switches the app to it; no ViewModel changes, because they depend on the interface:
class CachedBookRepository @Inject constructor(
private val dao: BookDao,
private val seed: AssetBookRepository, // the network takes this role in 5.27
private val sync: DataStore<SyncState>, // 5.26.2 what was cached, from where, when
) : BookRepository {
override suspend fun loadBooks(): List<Book> {
if (dao.count() == 0) { // the first query also opens the file
val now = System.currentTimeMillis()
dao.upsertAll(seed.loadBooks().map { it.toEntity(fetchedAt = now) })
sync.updateData { SyncState(source = "assets", books = dao.count(), at = now) }
Log.d("BookNestDB", "cache was empty: seeded ${dao.count()} books")
} else {
Log.d("BookNestDB", "cache hit, last sync: ${sync.data.first()}")
}
...After clearing the app's data, the first launch pays for the slow source once and the next never touches it:
D/BookNestDB: cache was empty: seeded 6 books D/BookNestDB: read 6 books in 8.958961 ms ... D/BookNestDB: cache hit, last sync: SyncState(source=assets, books=6, at=1790286303059) D/BookNestDB: read 6 books in 549.121244 ms
The second read was slower because it was that process's first query, which opens the file and checks the schema; on this loaded emulator such cold reads took 64 to 663 ms. The catalog is now sorted by title (ORDER BY title), and the app works offline after its first run. It cannot yet notice new prices on the server; Offline-First makes it offline-first.