Documentation

State & Reactivity

$state declares state. $derived computes a value from it. $effect runs when it changes. A plain var or val is plain Kotlin.

Local state

A var declared with $state is state. Assign to it and whatever reads it redraws:

var count = $state(0)

<Button onClick={() => count++}>
  <Text>{count}</Text>
</Button>

compiles to

var count by remember { mutableStateOf(0) }

Reading count in markup subscribes to it, and assigning to it recomposes whatever read it. You never write remember, mutableStateOf, or .value yourself.

A plain var is a Kotlin variable. Changing it redraws nothing, and if the markup reads it anyway, that is a build error:

error[state-not-declared]: `count` is read by the markup but is not state; write `var count = $state(0)`

Nothing is inferred from how a declaration is used. What it is written as is what it is.

Lists

$state([…]) declares a list changed in place. .add, .removeAt, and .clear each redraw what reads the list. It is never reassigned, so it may be a val:

val todos = $state(["Buy milk"])

<Button onClick={() => todos.add("Ship")}>Add</Button>

To replace a list whole, hold a list value instead and assign it:

var todos = $state(listOf("Buy milk"))

<Button onClick={() => todos = todos + "Ship"}>Add</Button>

When state outlives the composable

A screen that only holds a couple of fields keeps them in rememberSaveable, which survives rotation. A screen fed by a loader, one that reads $screen.params, or one holding a $state of a type a Bundle cannot carry is kept for the screen instead — on what Android calls a ViewModel, generated for you. Nothing about the source changes either way; whitehall why <file> names where each $state is kept and why.

DeclarationSurvives rotationSurvives restart
var x = $state(0), a Bundle-safe typeYesNo
var x = $state(User()) in a componentNoNo
var x = $state(User()) in a screenYesNo
var x = 0Not stateNo
@store object XYesNo
@store(persist) object XYesYes

Derived values

$derived(expr) recomputes whenever anything expr reads changes:

var count = $state(0)
var multiplier = $state(2)

val doubled = $derived(count * 2)           // tracks count
val scaled = $derived(count * multiplier)   // tracks count and multiplier

compiles to

val doubled by remember { derivedStateOf { count * 2 } }

Dependencies are whatever the expression reads. There is no list to declare and no list to keep in sync when you edit the expression. A $derived is always a val, and what reads it redraws only when its value changes.

For a computation that spans more than one expression, use the block form:

val label = $derived {
  val n = items.size
  if (n == 1) "1 item" else "$n items"
}

The last expression in the block is the value.

A plain val is a Kotlin value, evaluated again on every redraw — so val total = items.sumOf { it.price } stays current without $derived. Reach for $derived for a computation worth not repeating, or for a value computed once: val seed = $derived(Random.nextInt(30)) reads no state, so it runs once.

In ordinary code val x = expr runs on every read, and quietly changing that for some declarations and not others would make the language guess. $derived says "this one is cached and tracked" out loud, so a plain val keeps meaning exactly what it means in Kotlin.

See it happen

The source is editable, and the Kotlin is compiled here in your browser by the same compiler the CLI uses — so it is what you would actually get, not a transcription of it.

Counter.wh
import $lib.components.ui.Button

var count = $state(0)

<Column gap={16}>
  <Text>Count: {count}</Text>
  <Button onClick={() => count++}>Increment</Button>
</Column>
Kotlin waiting
Scroll into view to compile.

$state(0) declares state. Reading count in markup subscribes to it; assigning to it redraws whatever read it.

Effects

$effect runs when its screen or component appears, and again whenever the state it reads changes. It stops when the screen or component leaves:

var count = $state(0)

$effect {
  println("Count changed to: $count")
  analytics.track("count", count)
}

compiles to

LaunchedEffect(Unit) {
    snapshotFlow {
        println("Count changed to: $count")
    }.collect {}
}

Use it for the things a value cannot express: logging, analytics, syncing to disk. If you are computing something, reach for $derived instead — a derived value only recomputes when read, while an effect runs whether anyone is looking or not.

A block that begins with val declarations reads its dependencies there, and runs the rest as a coroutine, so it can call a suspend function:

$effect {
  val q = query
  results = search(q)
}
Assigning to state you read inside an $effect block makes it re-trigger itself. Derive instead of assigning, or move the write behind an event handler.

Collections

Bracket literals build lists, and mutability follows val versus var:

val numbers = [1, 2, 3]     // listOf(1, 2, 3)
val todos = $state(["Buy milk"])

A $state([…]) list is state, so pushing to it recomposes the list that renders it. See @for for iterating one.

Ranges

val simple  = 1..10       // (1..10).toList()
val stepped = 0..100:2    // step 2
val down    = 10..1:-1    // downTo

Inside a function

In a function body, var n = $state(0) is mutableStateOf(0) and val d = $derived(n * 2) is derivedStateOf { n * 2 }. Nothing is remembered: a function is not a screen.

Classes

class Cart {
  var items = $state(listOf<Item>())
  val total = $derived(items.sumOf { it.price })

  fun add(item: Item) {
    items = items + item
  }
}

A class with $state fields is a Kotlin class whose fields are state. Whoever makes one holds it: val cart = Cart() in a component is remember { Cart() }, made once. A plain var field is a plain field. The class's suspend functions stay suspend; a handler that calls one launches it.

Explicit derivedStateOf

The Compose primitive is still available when you want it, which is worth knowing for an expression that is expensive enough to think about:

var items = $state(listOf<Item>())
val sorted = derivedStateOf { items.sortedBy { it.name } }

Prefer $derived { … } for anything ordinary.

See Also