Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 47 additions & 3 deletions docs/userguide/common-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,59 @@ simply by rendering various children. It may just combine the renderings of mult
use its props to determine which of a set of children to render. Such workflows can often be
stateless.

## One-and-done Workflows (RenderingT v. OutputT)
## Skippable Workflows

A common question is “why can’t I emit output from `initialState`,” or “what if my Workflow realizes it doesn’t actually need to run? The most efficient, and most expressive, way to handle this is to use an optional or conditional `Rendering` type, and an `Output` of [`Never`](https://nshipster.com/never/)/[`Nothing`](https://medium.com/@agrawalsuneet/the-nothing-type-kotlin-2e7df43b0111).
* Why can’t I emit output from `initialState`?
* Why can’t I emit output from `render`?
* Why can't I change states during `render`?
* What if while rendering my Workflow realizes it doesn’t actually need to run?

The best pattern we've found for all of these situations is a "skippable workflow".
The idea is that a workflow that performs work that may not actually be necessary
provides an extra function for parents to call.
The parent can call this during the upstream action
where it's deciding whether or not to enter the state
in which the child would be run.

Perhaps it's a simple boolean function indicating whether or not the child actually has any work to do:

```kotlin
state = if (stepTwoWorkflow.isThisNecessary()) RunStepTwo else RunStepThree
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only works if the stepTwoWorkflow is created and stored in the state a head of time, before you get to the apply(action) method. If your stepTwoWorkflow can't be created until you get into the apply(action) with a specific action/state, then you are out of luck.

Copy link
Copy Markdown
Contributor Author

@rjrjr rjrjr Jul 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typically in Kotlin child workflows are DI injected (available as constructor parameters of the parent), and actions are inner objects of the parent workflow with access to its injected dependencies.

In Swift, didn't a change just land to make the parent workflow available from the action? cc @jamieQ

```

Perhaps it can return the usual output, or null to indicate that async work is required:

```kotlin
state = stepTwoWorkflow.instantResponse(stepTwoInput)?.let { RunStepThree(it) }
?: RunStepTwo
```

A particularly nice aspect of this approach is how it keeps
the knowledge encapsulated by the skippable workflow from leaking.

Any hacks you might resort during `initialState` or `render`
to make it seem like a workflow is short circuiting are big smells.
They are guaranteed to cause performance problems that compound,
and add needless complexity.

## Optional renderings (using RenderT instead of OutputT)

Besides the [skippable workflow](#skippable-workflows) pattern described above,
another way a child can communicate to its parent that it has no work to do
is with a nullable or otherwise un-renderable rendering type.

Imagine a `PromptForPermissionMaybeWorkflow`, that renders a UI to get a passcode, but only if that permission has not already been granted. If you make its `RenderingT` nullable (e.g. `Screen?`), it can return `null` to indicate that its job is done. Its callers will be synchronously informed that the coast is clear, and can immediately render what they actually care about.

Another variation of this pattern is to use a sealed class / enum type for `Rendering`, with a `Working` type that implements `Screen`, and a unviewable `Finished` type that carries the work product.

A good rule of thumb for choosing between using `Rendering` or `Output` is to remember that `Output` is event-like, and is always asynchronous. A parent waiting for an output must be given something to render in the meantime. Using `Rendering` is a great idiom for a one-and-done workflow tasked with providing a single product, especially one that might be available instantly.
Either way, it's a good idea to use an an output type of
[`Never`](https://nshipster.com/never/)/[`Nothing`](https://medium.com/@agrawalsuneet/the-nothing-type-kotlin-2e7df43b0111)
in a case like this, to keep it crystal clear how the child reports to the parent.

This pattern requires that the parent will not need to change state or emit output
based on the returned rendering type.
The parent must be able to choose some other child to run or something else to render immediately.
If that is not practical, go [skippable](#skippable-workflows) instead.

## Props values v. Injected Dependencies

Expand Down