Local Catalog Cache

A Local Cache for BookNest's Catalog

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:

data/BookRepository.kt: Room as the source of truth (excerpt)Kotlin
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:

Output of 147
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.