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.