| name | create-workflow |
| description | Create Square Workflow classes (StatefulWorkflow or StatelessWorkflow). Use when creating workflows, implementing state machines, handling async operations with Workers, or when user mentions "new workflow", "workflow", "state machine", or "StatefulWorkflow". |
Create Workflow
Create properly structured Square Workflow classes following library conventions.
Quick Start
To create a new workflow, gather:
- Workflow name (e.g.,
Login)
- Stateful or Stateless ā does it need internal state?
- Props ā input data from parent (
Unit if none)
- Output ā events emitted to parent (
Nothing if none)
- Rendering ā UI model returned each render pass (typically a
Screen data class)
Step 1: Choose Workflow Type
StatefulWorkflow ā use when you need internal state:
- Form data, loading states, error states
- Multi-step flows or navigation
- State that changes in response to events
StatelessWorkflow ā use when rendering is a direct function of props:
- Simple pass-through or composition of child workflows
- No internal state needed
- Rendering derived entirely from props and child renderings
Step 2: Define Types
Props (input from parent)
data class MyWorkflowProps(val userId: String)
State (StatefulWorkflow only)
State should use any Kotlin types - often a data class. If your state needs to be persisted
to disk via a Snapshot it needs to be able to be converted to a ByteString.
data class MyState(val name: String, val isLoading: Boolean)
sealed interface MyState {
data object Loading : MyState
data class Loaded(val data: String) : MyState
data class Error(val message: String) : MyState
}
Output (events to parent)
sealed interface MyOutput {
data object Completed : MyOutput
data class Selected(val item: Item) : MyOutput
}
Rendering (view model)
Create in a separate file named [Feature]Screen.kt:
data class MyScreen(
val title: String,
val isLoading: Boolean,
val onAction: () -> Unit,
val onItemSelected: (Item) -> Unit
) : Screen
Step 3: Create Workflow Class
StatefulWorkflow ā Object Pattern (no dependencies)
Use when the workflow has no injected dependencies:
object MyWorkflow : StatefulWorkflow<MyProps, MyState, MyOutput, MyScreen>() {
override fun initialState(props: MyProps, snapshot: Snapshot?): MyState =
MyState.Loading
override fun render(
renderProps: MyProps,
renderState: MyState,
context: RenderContext
): MyScreen {
return MyScreen(
title = renderProps.title,
isLoading = renderState is MyState.Loading,
onAction = context.eventHandler("onAction") {
setOutput(MyOutput.Completed)
},
onItemSelected = context.eventHandler("onItemSelected") { item: Item ->
state = MyState.Loaded(item.name)
}
)
}
override fun snapshotState(state: MyState): Snapshot? = null
}
StatefulWorkflow ā Class Pattern (with dependencies)
Use when the workflow needs injected dependencies:
class MyWorkflow(
private val repository: DataRepository,
) : StatefulWorkflow<MyProps, MyState, MyOutput, MyScreen>() {
override fun initialState(props: MyProps, snapshot: Snapshot?): MyState =
MyState.Loading
override fun render(
renderProps: MyProps,
renderState: MyState,
context: RenderContext
): MyScreen {
context.runningWorker(
Worker.from { repository.fetchData(renderProps.id) },
key = "fetchData-${renderProps.id}"
) { result ->
action("dataLoaded") {
state = MyState.Loaded(result)
}
}
return MyScreen(
title = renderProps.title,
isLoading = renderState is MyState.Loading,
onAction = context.eventHandler("onAction") {
setOutput(MyOutput.Completed)
}
)
}
override fun snapshotState(state: MyState): Snapshot? = null
}
StatelessWorkflow
object MyWorkflow : StatelessWorkflow<MyProps, MyOutput, MyScreen>() {
override fun render(
renderProps: MyProps,
context: RenderContext
): MyScreen {
return MyScreen(
title = renderProps.title,
onAction = context.eventHandler("onAction") { action: String ->
setOutput(MyOutput.ActionSelected(action))
}
)
}
}
Factory Functions (inline definitions)
For simple workflows that don't need a named class:
val counterWorkflow = Workflow.stateful<Unit, Int, Nothing, Int>(
initialState = 0,
render = { state ->
state
}
)
val greetingWorkflow = Workflow.stateless<String, Nothing, String> { name ->
"Hello, $name!"
}
Workers (Async Operations)
Use Workers for async work. Never perform side effects directly in render().
val worker = Worker.from { api.fetchUser(userId) }
val worker = repository.observeUpdates().asWorker()
class FetchWorker(private val id: String) : Worker<Data> {
override fun run(): Flow<Data> = flow { emit(api.fetch(id)) }
override fun doesSameWorkAs(otherWorker: Worker<*>): Boolean =
otherWorker is FetchWorker && otherWorker.id == id
}
context.runningWorker(worker, key = "fetch") { result ->
action("fetched") { state = MyState.Loaded(result) }
}
Child Workflows
val childScreen = context.renderChild(
child = ChildWorkflow,
props = ChildProps(renderProps.itemId)
) { childOutput ->
action("childOutput") {
when (childOutput) {
is ChildOutput.Done -> setOutput(MyOutput.Completed)
is ChildOutput.Back -> state = MyState.Initial
}
}
}
val childScreen = context.renderChild(ChildWorkflow, props = childProps)
Side Effects
For coroutine work that doesn't produce workflow output:
context.runningSideEffect("trackScreen") {
analytics.trackScreenView("my_screen")
}
Critical Rules
- Never perform side effects in
render() ā render is called multiple times per state.
Use runningWorker or runningSideEffect.
- Don't capture
renderState in lambdas ā In eventHandler and action blocks, use the
state property from the Updater receiver, not the renderState parameter.
- Always provide
name to eventHandler ā Required for Compose stability and debugging.
- Use
setOutput() to emit output ā Call at most once per action.
- Return
null from snapshotState unless you need state persistence across process death.
Naming Conventions
| Type | Convention | Example |
|---|
| Workflow | [Feature]Workflow | LoginWorkflow |
| Props | [Feature]Props or Unit | LoginProps |
| State | [Feature]State or nested type | LoginState |
| Output | [Feature]Output or Nothing | LoginOutput |
| Rendering | [Feature]Screen (separate file) | LoginScreen |
File Organization
feature/
āāā MyWorkflow.kt # Workflow + Props + State + Output
āāā MyScreen.kt # Rendering class (separate file)
Required Imports
import com.squareup.workflow1.StatefulWorkflow
import com.squareup.workflow1.Snapshot
import com.squareup.workflow1.action
import com.squareup.workflow1.Worker
import com.squareup.workflow1.runningWorker
import com.squareup.workflow1.asWorker
import com.squareup.workflow1.ui.Screen
import com.squareup.workflow1.Workflow
import com.squareup.workflow1.stateful
import com.squareup.workflow1.stateless
Documentation
Output
When creating a workflow, always:
- Determine whether StatefulWorkflow or StatelessWorkflow is needed
- Define Props, State (if stateful), Output, and Rendering types
- Create the workflow class with proper type parameters
- Create the Screen rendering in a separate file
- Use
eventHandler("name") for UI events
- Use Workers for async operations
- Ensure all imports are correct