@@ -8,6 +8,7 @@ import kotlinx.coroutines.CoroutineScope
88import kotlinx.coroutines.flow.Flow
99import kotlinx.coroutines.flow.StateFlow
1010import 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,77 @@ 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+ * // runCatching can catch CancellationException, so check.
195+ * ensureActive()
196+ *
197+ * while (result.isFailure) {
198+ * showScreen {
199+ * RetryScreen(
200+ * message = "Failed: ${result.exceptionOrNull()}",
201+ * onRetryClicked = { continueWith(Unit) },
202+ * onCancelClicked = { goBack() }
203+ * )
204+ * }
205+ *
206+ * // Try again.
207+ * result = runCatching { action() }
208+ * ensureActive()
209+ * }
210+ *
211+ * // We only leave the loop when action succeeded.
212+ * return result.getOrThrow()
213+ * }
214+ * ```
96215 */
97216 abstract suspend fun BackStackScope.runBackStack (
98217 props : StateFlow <PropsT >,
@@ -134,6 +253,12 @@ public sealed interface BackStackParentScope {
134253 * that is relevant within a backstack, and it's not possible to know whether the parent supports
135254 * back. What you probably want is to emit an output instead to tell the parent to go back.
136255 *
256+ * If the coroutine calling [showWorkflow] is cancelled, the workflow stops being rendered and its
257+ * rendering will be removed from the backstack.
258+ *
259+ * See [BackStackWorkflow.runBackStack] for high-level documentation about how to use this method
260+ * to implement a backstack workflow.
261+ *
137262 * @param props The props passed to [workflow] when rendering it. [showWorkflow] will suspend
138263 * until the first value is emitted. Consider transforming the [BackStackWorkflow.runBackStack]
139264 * props [StateFlow] or using [flowOf].
@@ -149,6 +274,12 @@ public sealed interface BackStackParentScope {
149274 /* *
150275 * Shows the screen produced by [screenFactory]. Suspends untilBackStackNestedScope.goBack] is
151276 * called.
277+ *
278+ * If the coroutine calling [showScreen] is cancelled, the rendering will be removed from the
279+ * backstack.
280+ *
281+ * See [BackStackWorkflow.runBackStack] for high-level documentation about how to use this method
282+ * to implement a backstack workflow.
152283 */
153284 suspend fun <R > showScreen (
154285 screenFactory : BackStackScreenScope <R >.() -> Screen
0 commit comments