Skip to content

Store

Databases & CachingCaching/Distributed DataKotlin

What it is

Store from Dropbox coordinates network fetching and local caching behind one API, providing single-source-of-truth data loading for Kotlin applications.

Build a Store from a Fetcher and an optional SourceOfTruth. Requests declare whether cached data is acceptable, and the Store emits loading, data and error states as a Flow.

Installation

implementation("org.mobilenativefoundation.store:store5:5.1.0")

Getting started

The smallest useful thing you can do with it, and what each part means.

Fetcher plus source of truth
val bookStore = StoreBuilder
    .from(
        fetcher = Fetcher.of { id: Int -> api.getBook(id) },
        sourceOfTruth = SourceOfTruth.of(
            reader = { id -> dao.observeBook(id) },
            writer = { _, book -> dao.upsert(book) },
            delete = { id -> dao.delete(id) },
        ),
    )
    .build()

// Cached if available, otherwise fetched.
bookStore.stream(StoreReadRequest.cached(42, refresh = true))
    .collect { response ->
        when (response) {
            is StoreReadResponse.Loading -> showSpinner()
            is StoreReadResponse.Data    -> render(response.value)
            is StoreReadResponse.Error   -> showError(response.errorMessageOrNull())
        }
    }
With refresh = true the Store emits the cached value immediately and then the fresh one — so the screen shows content instantly and updates when the network responds.

Advanced usage

Where the library earns its place over a simpler alternative.

Deduplication and cache policy
val store = StoreBuilder
    .from(fetcher, sourceOfTruth)
    .cachePolicy(
        MemoryPolicy.builder<Int, Book>()
            .setMaxSize(100)
            .setExpireAfterWrite(5.minutes)
            .build()
    )
    .build()

// Concurrent requests for the same key share one network call.
coroutineScope {
    repeat(10) { launch { store.get(42) } }   // exactly one API request
}

store.fresh(42)          // bypass the cache entirely
store.clear(42)          // invalidate one key
Request deduplication is quietly one of the most valuable parts: ten composables asking for the same book on one screen produce one network call rather than ten.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

Stale data is shown indefinitely
The request used cached() without refresh. Pass refresh = true, or call fresh() explicitly.
Writes do not appear in the UI
The reader Flow is not observing the same rows the writer updates. Ensure the DAO returns an observable query.

Best practices

  • Always provide a SourceOfTruth for anything that should work offline.
  • Use cached(refresh = true) for screens so users see data immediately and it updates behind them.
  • Set an explicit memory policy; unbounded caches grow until the app is killed.
  • Handle StoreReadResponse.Error rather than only rendering the data case.

Background

Why it exists, and what it was reacting to.

Store encodes the pattern every mobile app rewrites: read from cache, fetch from network, write back, and emit updates. Getting the offline and refresh cases right by hand is fiddly and frequently subtly wrong.