Skip to content

Commit 1380ffd

Browse files
authored
Skippable instead of One and Done
The One and Done entry was bad advice, or at least incomplete. Updated to describe the Skippable Workflow pattern we've been using with a lot of success lately.
1 parent e1eb35c commit 1380ffd

File tree

1 file changed

+49
-3
lines changed

1 file changed

+49
-3
lines changed

docs/userguide/common-patterns.md

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,61 @@ simply by rendering various children. It may just combine the renderings of mult
3434
use its props to determine which of a set of children to render. Such workflows can often be
3535
stateless.
3636

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

39-
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).
39+
* Why can’t I emit output from `initialState`?
40+
* Why can’t I emit output from `render`?
41+
* Why can't I change states during `render`?
42+
* What if while rendering my Workflow realizes it doesn’t actually need to run?
43+
44+
The best pattern we've found for all of these situations is a "skippable workflow".
45+
The idea is that a workflow that performs work that may not actually be necessary
46+
provides an extra function for parents to call.
47+
The parent can call this during the upstream action
48+
where it's deciding whether or not to enter the state
49+
in which the child would be run.
50+
51+
Perhaps it's a simple boolean function indicating whether or not the child actually has any work to do:
52+
53+
```kotlin
54+
state = if (stepTwoWorkflow.isThisNecessary()) RunStepTwo else RunStepThree
55+
```
56+
57+
Perhaps it can return the usual output, or null to indicate that async work is required:
58+
59+
```kotlin
60+
state = stepTwoWorkflow.instantResponse(stepTwoInput)?.let { RunStepThree(it) }
61+
?: RunStepTwo
62+
```
63+
64+
A particularly nice aspect of this approach is how it keeps
65+
the knowledge encapsulated by the skippable workflow from leaking.
66+
67+
Any hacks you might resort during `initialState` or `render`
68+
to make it seem like a workflow is short circuiting are big smells.
69+
They are guaranteed to cause performance problems that compound,
70+
and add needless complexity.
71+
72+
## Optional renderings (using RenderT instead of OutputT)
73+
74+
Besides the [skippable workflow](#skippable-workflows) pattern described above,
75+
another way a child can communicate to its parent that it has no work to do
76+
is with a nullable or otherwise un-renderable rendering type.
77+
(If you're using this patt
78+
and .
4079

4180
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.
4281

4382
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.
4483

45-
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.
84+
Either way, it's a good idea to use an an output type of
85+
[`Never`](https://nshipster.com/never/)/[`Nothing`](https://medium.com/@agrawalsuneet/the-nothing-type-kotlin-2e7df43b0111)
86+
in a case like this, to keep it crystal clear how the child reports to the parent.
87+
88+
This pattern requires that the parent will not need to change state or emit output
89+
based on the returned rendering type.
90+
The parent must be able to choose some other child to run or something else to render immediately.
91+
If that is not practical, go [skippable](#skippable-workflows) instead.
4692

4793
## Props values v. Injected Dependencies
4894

0 commit comments

Comments
 (0)