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.