Hello Kmposable
This walkthrough shows the smallest flow end-to-end. You’ll define a node, run it headlessly, render it in Compose, and test it without UI.
1. Install Dependencies
dependencies {
implementation("dev.goquick.kmposable:core:<version>")
implementation("dev.goquick.kmposable:compose:<version>")
testImplementation("dev.goquick.kmposable:test:<version>")
}
Replace <version> with the latest release from Maven Central.
2. Define a Node
data class CounterState(val value: Int = 0)
sealed interface CounterEvent { object Increment : CounterEvent; object Decrement : CounterEvent }
class CounterNode(parentScope: CoroutineScope) :
StatefulNode<CounterState, CounterEvent, Nothing>(parentScope, CounterState()) {
override fun onEvent(event: CounterEvent) {
when (event) {
CounterEvent.Increment -> updateState { it.copy(value = it.value + 1) }
CounterEvent.Decrement -> updateState { it.copy(value = it.value - 1) }
}
}
}
3. Run a NavFlow
val navFlow = NavFlow(scope, CounterNode(scope)).apply { start() }
navFlow.sendEvent(CounterEvent.Increment)
println(navFlow.navState.value.top.state.value) // 1
4. Render with Compose
@Composable
fun CounterScreen() {
val navFlow = rememberNavFlow { scope -> NavFlow(scope, CounterNode(scope)) }
val renderer = remember {
nodeRenderer<Nothing> {
register<CounterNode> { node ->
val state by node.state.collectAsState()
CounterUi(state.value) { node.onEvent(CounterEvent.Increment) }
}
}
}
NavFlowHost(navFlow, renderer)
}
5. Test Headlessly
@Test
fun counterIncrements() = runTest {
val factory = SimpleNavFlowFactory<Nothing> { NavFlow(this, CounterNode(this)) }
factory.createTestScenario(this)
.start()
.send(CounterEvent.Increment)
.apply {
val top = navFlow.navState.value.top as CounterNode
assertEquals(1, top.state.value)
}
.finish()
}
That’s it—you’ve built a flow that works without UI, renders in Compose, and is fully testable. Continue with the Guides to dive deeper.