Concurrency
The document API is fully synchronous and non-suspending.
get(), set(), update(), delete(),
exists(), and every field delegate are plain function calls — none of them are
suspend functions, and none of them require a coroutine scope to call. MMKV is
memory-mapped, so a read or write is a memory operation, not I/O — there is nothing to suspend
for.
// no coroutine scope needed for any of these:
note.set(Note(title = "Pick up milk"))
val current = note.get()
note.update(Note::done, true)
Writes are serialized per document
Every write to a given document goes through a lightweight, non-suspending lock scoped to that
document — not a coroutine Mutex. This is what makes a multi-field
update { current -> ... } atomic from the caller's perspective: the read of the
current value and the write of the new one happen as one uninterrupted unit, so a concurrent
reader never observes a half-applied update. Because the lock is non-suspending, it composes
safely with plain synchronous call sites — there's no risk of it blocking a coroutine dispatcher
in a way that would starve other coroutines.
What is dispatcher-governed
Only flow() and stateFlow() collection runs on a
configurable dispatcher — Dispatchers.Default by default, since re-reading and
decoding a value on each change bus emission is CPU-bound work (see
Reactivity Model). This dispatcher only affects how
a Flow is collected; it has no bearing on the document operations themselves, which
remain synchronous regardless of which dispatcher a caller happens to be on.