Read & Write

Three call shapes cover reading and writing a document, and each carries a distinct intent — knowing which one to reach for is the API's central nuance.

note.get(): Note?                       // current value, or null if absent
note.set(value)                         // replace the whole document
note.update { current -> ... }          // update: build over the current value (or defaults)
note.update(Note::done, true)           // single-field update: writes just that key, no read
note.delete()                           // remove the document and all its field keys
note.exists(): Boolean                  // true if any field key is stored

get() — never throws for "missing"

get() returns the current value, or null if the document was never written. A missing document is not an error condition:

val current: Note? = note.get()

set(value) — replace

Supply a complete object and it replaces the document outright — every field, whether it changed or not:

note.set(Note(title = "Pick up milk", body = "2%, not whole"))

update { current -> ... } — update, several fields at once

The builder receives the current value (or the type's defaults, if the document is absent) as an explicit current parameter and returns a new value via copy() — matching kotlinx.coroutines.flow.MutableStateFlow.update { current -> ... }'s shape. Untouched fields keep exactly the value they had:

note.update { current ->
    current.copy(
        title = "Pick up oat milk",
        body = "The barista-approved kind",
    )
}

This is a read-modify-write that runs under the document's write lock, so a multi-field update is atomic from the caller's perspective — a concurrent reader never observes a half-applied write.

update(prop, value) — a single field, no read

When only one field needs to change, update(prop, value) writes just that field's decomposed key directly — no read of the rest of the document:

note.update(Note::done, true)

Reach for update(prop, value) when you're changing exactly one field and don't need the current value to compute the new one; reach for update { current -> ... } when an edit touches more than one field together, or the new value depends on the current one.

delete() and exists()

note.delete()            // removes the document and all its field keys
println(note.exists())   // false

See Concurrency for exactly how the per-document write lock keeps a multi-field update atomic, or Field Delegates if you'd rather bind a single field as a property than call update(prop, value) directly.