Skip to content

Commit 5fc252c

Browse files
moar docs so i don't forget some cool usage patterns
1 parent 702e06f commit 5fc252c

File tree

1 file changed

+134
-4
lines changed

1 file changed

+134
-4
lines changed

samples/containers/thingy/src/main/java/com/squareup/sample/thingy/BackStackWorkflow.kt

Lines changed: 134 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import kotlinx.coroutines.CoroutineScope
88
import kotlinx.coroutines.flow.Flow
99
import kotlinx.coroutines.flow.StateFlow
1010
import kotlinx.coroutines.flow.flowOf
11+
import kotlinx.coroutines.launch
1112

1213
/**
1314
* Creates a [BackStackWorkflow]. See the docs on [BackStackWorkflow.runBackStack] for more
@@ -45,11 +46,58 @@ public abstract class BackStackWorkflow<PropsT, OutputT> :
4546
* Show renderings by calling [BackStackScope.showScreen]. Show child workflows by calling
4647
* [BackStackScope.showWorkflow]. Emit outputs by calling [emitOutput].
4748
*
48-
* # Examples
49+
* # Showing a screen
50+
*
51+
* ```
52+
* backStackWorkflow { _, _ ->
53+
* // Suspends until continueWith is called.
54+
* val result = showScreen {
55+
* MyScreenClass(
56+
* // Returns "finished" from showScreen.
57+
* onDoneClicked = { continueWith("finished") },
58+
* )
59+
* }
60+
* }
61+
* ```
62+
*
63+
* # Showing a workflow
64+
*
65+
* ```
66+
* backStackWorkflow { _, _ ->
67+
* // Suspends until an onOutput lambda returns a value.
68+
* val result = showWorkflow(
69+
* childWorkflow,
70+
* props = flowOf(childProps)
71+
* onOutput = { output ->
72+
* // Returns "finished: …" from showWorkflow.
73+
* return@showWorkflow "finished: $output"
74+
* }
75+
* )
76+
* }
77+
* ```
78+
*
79+
* # Emitting output
80+
*
81+
* The second parameter to the [runBackStack] function is an [emitOutput] function that will send
82+
* whatever you pass to it to this workflow's parent as an output.
83+
* ```
84+
* backStackWorkflow { _, emitOutput ->
85+
* showWorkflow(
86+
* childWorkflow,
87+
* props = flowOf(childProps)
88+
* onOutput = { output ->
89+
* // Forward the output to parent.
90+
* emitOutput(output)
91+
* }
92+
* )
93+
* }
94+
* ```
95+
*
96+
* # Nested vs serial calls
4997
*
5098
* The backstack is represented by _nesting_ `showWorkflow` calls. Consider this example:
5199
* ```
52-
* backStackWorkflow {
100+
* backStackWorkflow { _, _ ->
53101
* showWorkflow(child1) {
54102
* showWorkflow(child2) {
55103
* showWorkflow(child3) {
@@ -66,7 +114,7 @@ public abstract class BackStackWorkflow<PropsT, OutputT> :
66114
*
67115
* Contrast with calls in series:
68116
* ```
69-
* backStackWorkflow {
117+
* backStackWorkflow { _, _ ->
70118
* showWorkflow(child1) { finishWith(Unit) }
71119
* showWorkflow(child2) { finishWith(Unit) }
72120
* showWorkflow(child3) { }
@@ -77,7 +125,7 @@ public abstract class BackStackWorkflow<PropsT, OutputT> :
77125
*
78126
* These can be combined:
79127
* ```
80-
* backStackWorkflow {
128+
* backStackWorkflow { _, _ ->
81129
* showWorkflow(child1) {
82130
* showWorkflow(child2) {
83131
* // goBack(), or
@@ -93,6 +141,76 @@ public abstract class BackStackWorkflow<PropsT, OutputT> :
93141
* `child2` emits an output, it can decide to call `goBack` to show `child1` again, or call
94142
* `finishWith` to replace itself with `child3`. `child3` can also call `goBack` to show `child`
95143
* again.
144+
*
145+
* To push another screen on the backstack from a non-workflow screen, [launch] a coroutine:
146+
* ```
147+
* backStackScreen { _, _ ->
148+
* showScreen {
149+
* MyScreen(
150+
* onEvent = {
151+
* launch {
152+
* showWorkflow(…)
153+
* }
154+
* }
155+
* }
156+
* }
157+
* }
158+
* ```
159+
*
160+
* # Cancelling screens
161+
*
162+
* Calling [BackStackScope.showScreen] or [BackStackScope.showWorkflow] suspends the caller until
163+
* that workflow/screen produces a result. They handle coroutine cancellation too: if the calling
164+
* coroutine is cancelled while they're showing, they are removed from the backstack.
165+
*
166+
* This can be used to, for example, update a screen based on a flow:
167+
* ```
168+
* backStackWorkflow { props, _ ->
169+
* props.collectLatest { prop ->
170+
* showScreen {
171+
* MyScreen(message = prop)
172+
* }
173+
* }
174+
* }
175+
* ```
176+
* This example shows the props received from the parent to the user via `MyScreen`. Every time
177+
* the parent passes a new props, the `showScreen` call is cancelled and called again with the
178+
* new props, replacing the old instance of `MyScreen` in the backstack with a new one. Since
179+
* both instances of `MyScreen` are compatible, this is not a navigation event but just updates
180+
* the `MyScreen` view factory.
181+
*
182+
* # Factoring out code
183+
*
184+
* You don't have to keep all the logic for your backstack in a single function. You can pull out
185+
* functions, just make them extensions on [BackStackParentScope] to get access to `showScreen`
186+
* and `showRendering` calls.
187+
*
188+
* E.g. here's a helper that performs some suspending task and shows a retry screen if it fails:
189+
* ```
190+
* suspend fun <R> BackStackParentScope.userRetriable(
191+
* action: suspend () -> R
192+
* ): R {
193+
* var result = runCatching { action() }
194+
* ensureActive()
195+
*
196+
* while (result.isFailure) {
197+
* showScreen {
198+
* RetryScreen(
199+
* message = "Failed: ${result.exceptionOrNull()}",
200+
* onRetryClicked = { continueWith(Unit) },
201+
* onCancelClicked = { goBack() }
202+
* )
203+
* }
204+
*
205+
* // Try again.
206+
* result = runCatching { action() }
207+
* ensureActive()
208+
* }
209+
*
210+
* // We only leave the loop when action succeeded.
211+
* return result.getOrThrow()
212+
* }
213+
* ```
96214
*/
97215
abstract suspend fun BackStackScope.runBackStack(
98216
props: StateFlow<PropsT>,
@@ -134,6 +252,12 @@ public sealed interface BackStackParentScope {
134252
* that is relevant within a backstack, and it's not possible to know whether the parent supports
135253
* back. What you probably want is to emit an output instead to tell the parent to go back.
136254
*
255+
* If the coroutine calling [showWorkflow] is cancelled, the workflow stops being rendered and its
256+
* rendering will be removed from the backstack.
257+
*
258+
* See [BackStackWorkflow.runBackStack] for high-level documentation about how to use this method
259+
* to implement a backstack workflow.
260+
*
137261
* @param props The props passed to [workflow] when rendering it. [showWorkflow] will suspend
138262
* until the first value is emitted. Consider transforming the [BackStackWorkflow.runBackStack]
139263
* props [StateFlow] or using [flowOf].
@@ -149,6 +273,12 @@ public sealed interface BackStackParentScope {
149273
/**
150274
* Shows the screen produced by [screenFactory]. Suspends untilBackStackNestedScope.goBack] is
151275
* called.
276+
*
277+
* If the coroutine calling [showScreen] is cancelled, the rendering will be removed from the
278+
* backstack.
279+
*
280+
* See [BackStackWorkflow.runBackStack] for high-level documentation about how to use this method
281+
* to implement a backstack workflow.
152282
*/
153283
suspend fun <R> showScreen(
154284
screenFactory: BackStackScreenScope<R>.() -> Screen

0 commit comments

Comments
 (0)