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.
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())
}
}Advanced usage
Where the library earns its place over a simpler alternative.
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 keyErrors 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.
