Persistence
State survives process death through slot persistence: state slots addressed by stable ids, bridged to a storage backend explicitly. Also here: the theme battery, Kinetica's other ambient state module.
Stable slot ids
val DraftSlot = SlotId(
moduleId = "todo",
functionFqName = "app.TodoApp",
declarationOrdinal = 2,
disambiguator = "draft",
)
var draft by state(slotId = DraftSlot, persistent = true) { "" }SlotId is the durable address of a piece of state. The compiler plugin generates one for every keyless state(persistent = true) call; declare one explicitly when you need an address that survives refactors verbatim. persistent = true marks the slot as durable metadata — it does not write to disk by itself; durability comes from the save/restore bridge below.
Saving and restoring
val backend = JsonSlotPersistenceBackend() // reference backend: JSON over an in-memory store
// on save points (suspend):
scope.saveSlot(DraftSlot, backend, String.serializer())
// on startup — restore into the scope, then render with it:
val scope = ComponentScope(runtime)
scope.restoreSlot(DraftSlot, backend, String.serializer()) // parks the value in the scope
runtime.render(scope) {
var draft by persistentState(slotId = DraftSlot, restoredValue = readSlot(DraftSlot)) { "" }
}JsonSlotPersistenceBackend handles serialization but stores strings in memory — it is the reference implementation, not durable storage. Real durability comes from a StringSlotPersistenceStore over platform storage (see Backends below).
Writing null into a present slot removes the backend entry — cleared state does not resurrect on the next launch. Batch variants: persistentSlot(slotId, serializer) bindings + restoreSlots / saveSlots.
Backends
SlotPersistenceBackend is three suspend functions (read, write, remove). StringSlotPersistenceBackend handles JSON encoding over any StringSlotPersistenceStore — implement the string store over your platform storage. The shipped DataStore… / LocalStorage… / NSUserDefaults… backends are namespaced wrappers over a store you provide, not built-in platform integrations.
Theming (kinetica-theme)
Ambient design tokens ride the context system:
provideTheme(DarkTheme.copy(direction = LayoutDirection.Rtl)) {
val t = theme()
host("div", props = mapOf("style" to "background:${t.colors.background}")) {
text(t.directional(ltr = "→", rtl = "←"))
row { … } // rows pick up the RTL direction automatically
}
}Theme(colors: ColorTokens, breakpoints: Breakpoints, direction)— serializable values;LightTheme/DarkThemeare provided.ColorTokens(background, surface, text, accent)— a minimal token set (not a full design system).- Breakpoints:
theme.breakpointFor(width)→Compact / Medium / Expanded. - Direction helpers:
layoutDirection(),isRtl(),theme.directional(ltr, rtl).