@@ -8,6 +8,7 @@ import kotlinx.coroutines.CoroutineScope
8
8
import kotlinx.coroutines.flow.Flow
9
9
import kotlinx.coroutines.flow.StateFlow
10
10
import kotlinx.coroutines.flow.flowOf
11
+ import kotlinx.coroutines.launch
11
12
12
13
/* *
13
14
* Creates a [BackStackWorkflow]. See the docs on [BackStackWorkflow.runBackStack] for more
@@ -45,11 +46,58 @@ public abstract class BackStackWorkflow<PropsT, OutputT> :
45
46
* Show renderings by calling [BackStackScope.showScreen]. Show child workflows by calling
46
47
* [BackStackScope.showWorkflow]. Emit outputs by calling [emitOutput].
47
48
*
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
49
97
*
50
98
* The backstack is represented by _nesting_ `showWorkflow` calls. Consider this example:
51
99
* ```
52
- * backStackWorkflow {
100
+ * backStackWorkflow { _, _ ->
53
101
* showWorkflow(child1) {
54
102
* showWorkflow(child2) {
55
103
* showWorkflow(child3) {
@@ -66,7 +114,7 @@ public abstract class BackStackWorkflow<PropsT, OutputT> :
66
114
*
67
115
* Contrast with calls in series:
68
116
* ```
69
- * backStackWorkflow {
117
+ * backStackWorkflow { _, _ ->
70
118
* showWorkflow(child1) { finishWith(Unit) }
71
119
* showWorkflow(child2) { finishWith(Unit) }
72
120
* showWorkflow(child3) { }
@@ -77,7 +125,7 @@ public abstract class BackStackWorkflow<PropsT, OutputT> :
77
125
*
78
126
* These can be combined:
79
127
* ```
80
- * backStackWorkflow {
128
+ * backStackWorkflow { _, _ ->
81
129
* showWorkflow(child1) {
82
130
* showWorkflow(child2) {
83
131
* // goBack(), or
@@ -93,6 +141,76 @@ public abstract class BackStackWorkflow<PropsT, OutputT> :
93
141
* `child2` emits an output, it can decide to call `goBack` to show `child1` again, or call
94
142
* `finishWith` to replace itself with `child3`. `child3` can also call `goBack` to show `child`
95
143
* 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
+ * ```
96
214
*/
97
215
abstract suspend fun BackStackScope.runBackStack (
98
216
props : StateFlow <PropsT >,
@@ -134,6 +252,12 @@ public sealed interface BackStackParentScope {
134
252
* that is relevant within a backstack, and it's not possible to know whether the parent supports
135
253
* back. What you probably want is to emit an output instead to tell the parent to go back.
136
254
*
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
+ *
137
261
* @param props The props passed to [workflow] when rendering it. [showWorkflow] will suspend
138
262
* until the first value is emitted. Consider transforming the [BackStackWorkflow.runBackStack]
139
263
* props [StateFlow] or using [flowOf].
@@ -149,6 +273,12 @@ public sealed interface BackStackParentScope {
149
273
/* *
150
274
* Shows the screen produced by [screenFactory]. Suspends untilBackStackNestedScope.goBack] is
151
275
* 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.
152
282
*/
153
283
suspend fun <R > showScreen (
154
284
screenFactory : BackStackScreenScope <R >.() -> Screen
0 commit comments