Documentation

State & Reactivity

A var is state. A $: in front of one makes it derive from other state. There is no third thing to learn.

Local state

Declare a var in a component's script and it becomes Compose state:

var count = 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.

When state outlives the composable

A screen that only holds a couple of fields keeps them in remember, which is lost on rotation. Past a certain complexity Whitehall promotes the whole script into a ViewModel instead, and the state survives configuration changes. That happens when a component has mutable state and any of:

  • a suspend function
  • a lifecycle hook — $onMount or $onDispose
  • three or more functions, which is taken as a sign of real state logic
var count = 0
suspend fun loadData() { … }      // this makes it a ViewModel
$onMount { loadData() }
PatternSurvives rotationUse for
var x = 0, simple scriptNoSmall forms, toggles
var x = 0, promotedYessuspend / lifecycle / 3+ functions
class X { var … }YesScreen state you name
@store object XYesApp-wide, lives the whole process

Derived values

Put $: in front of a declaration and it recomputes whenever anything it reads changes:

var count = 0
var multiplier = 2

$: var doubled = count * 2           // tracks count
$: var scaled = 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.

Why the marker? In ordinary code val x = expr runs once, and quietly changing that for some declarations and not others would make the language guess. $: says "this one re-runs" 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
var count = 0

val doubled = count * 2

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

A plain val is computed once, at first composition. Nothing in the Kotlin watches count, so doubled keeps whatever value it had when the screen was built.

Effects

A $: block runs its body whenever the state it reads changes:

var count = 0

$: {
  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 can't express: logging, analytics, syncing to disk. If you are computing something, reach for $: var instead — a derived value only recomputes when read, while an effect runs whether anyone is looking or not.

Assigning to state you read inside a $: 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)
var queue = [10, 20]        // mutableStateOf(mutableListOf(10, 20))

A var 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

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 = []
val sorted = derivedStateOf { items.sortedBy { it.name } }

Prefer $: var sorted = … for anything ordinary.

See Also