| 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 ā renderState is a snapshot from render time and
will be stale when the action fires. This includes local variables derived from renderState
(easy to miss!). Always read from state on the Updater receiver inside action/eventHandler
lambdas. For sealed state hierarchies, use safeAction<SpecificState>("name") which no-ops if
the state type has changed. See AGENTS.md "Common Pitfalls" for detailed examples.
- 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