Skip to content

State

State is an internal object managed by a formula. It is typically an immutable data class — transitions provide a new state instance which triggers re-evaluation. State is created by initialState(input) when the formula starts and used within evaluate() to define actions, child formulas, and build output.

class CounterFormula : Formula<Unit, CounterFormula.State, CounterOutput>() {

    data class State(
        val count: Int,
    )

    override fun initialState(input: Unit) = State(count = 0)

    override fun Snapshot<Unit, State>.evaluate(): Evaluation<CounterOutput> {
        return Evaluation(
            output = CounterOutput(
                count = state.count,
                onIncrement = context.onEvent {
                    transition(state.copy(count = state.count + 1))
                },
            )
        )
    }
}

Initial State

initialState(input) is called once when the formula first starts. It can use input to seed values.

override fun initialState(input: Input) = State(
    itemId = input.itemId,
    item = null,
)

It is also called again if key(input) changes — covered in Formula Key.

Transitions

State changes happen via the transition() DSL within event listeners and action handlers.

Update state — triggers re-evaluation:

context.onEvent { newName: String ->
    transition(state.copy(name = newName))
}

Side effect only — no state change:

context.callback {
    transition {
        analytics.trackSaveClicked()
    }
}

Both — update state and execute a side effect:

context.callback {
    transition(state.copy(saved = true)) {
        userService.save(state.data)
    }
}

Do nothing:

context.callback {
    none()
}

Side effects execute after state changes — the formula is in the correct state before any effect runs.

Responding to Input Changes

onInputChanged is called before evaluate() when input changes. It returns the new state to use for evaluation.

override fun onInputChanged(
    oldInput: Input,
    input: Input,
    state: State,
): State {
    return if (oldInput.itemId != input.itemId) {
        state.copy(item = null)
    } else {
        state
    }
}

Use this to reset or adjust state when a key piece of input changes.

Formula Key

key(input) defines the formula's identity. If the return value changes, the formula restarts — state is discarded and initialState is called again.

override fun key(input: Input) = input.itemId

Use this when a formula should fully reset when what it represents changes — for example, an item detail screen that should start fresh when navigating to a different item.