Sparround

Caching & offline-first strategies

Caching strategies sit behind the interview question "how does your app behave on a weak connection?":

  • Cache-then-network (stale-while-revalidate) — show cache first, refresh in parallel, replace on arrival. Best UX; with a staleness indicator on a balance screen.
  • Network-first, cache fallback — network first, cache if it fails. Where freshness is critical.
  • Cache-only / Network-only — special cases (offline mode / payment operations).

Cache by layer:

  • HTTP cache (OkHttp) — driven by Cache-Control headers; needs server cooperation.
  • In-memory cache — session-long, fastest.
  • DB cache (Room) — the backbone of offline-first: the DB is the single source of truth, the network updates the DB.

Invalidation is the hard part: TTL (time-based), event-based (the user made a transfer → the balance cache is stale), version-based (ETag/If-None-Match → 304).

kotlin
class TransactionRepository @Inject constructor(
    private val api: TransactionApi,
    private val dao: TransactionDao
) {
    // UI həmişə DB-yə baxır — offline-da da işləyir
    fun observeTransactions(accountId: String): Flow<List<Transaction>> =
        dao.observeByAccount(accountId).map { list -> list.map { it.toDomain() } }

    // Yeniləmə DB-ni yazır; Flow avtomatik yeni datanı ötürür
    suspend fun refresh(accountId: String): RefreshResult = try {
        val remote = api.getTransactions(accountId)
        dao.upsertAll(remote.map { it.toEntity() })
        RefreshResult.Success
    } catch (e: IOException) {
        RefreshResult.Offline   // UI: "son yenilənmə 14:32" göstərir
    }
}

An offline-first repository: read the DB, refresh from network

Banking boundaries: balances and transaction history get cached (with staleness indicators), but a payment is never shown "successful" from cache and never auto-queued offline — re-execution without user confirmation risks double charges. Sensitive responses should carry Cache-Control: no-store from the server; if they don't, enforce it with an interceptor.

🛠 Practice task

Combine the previous two tasks: write an offline-first repository with Retrofit + Room.

  • observeX() reads only from the DB; refresh() fetches from the network into the DB.
  • Turn on airplane mode and restart the app — the data must still appear.
  • Add a "last updated HH:mm" indicator to the UI, warning when it is older than 5 minutes.

Done when: the screen is never blank offline and the user can see the data is stale.

📚 Sources and documentation