Skip to content

Commit 82600bb

Browse files
rename to BackStackWorkflow, follow proper helper workflow pattern
1 parent c027265 commit 82600bb

File tree

2 files changed

+103
-82
lines changed

2 files changed

+103
-82
lines changed

samples/containers/thingy/src/main/java/com/squareup/sample/thingy/ThingyWorkflow.kt renamed to samples/containers/thingy/src/main/java/com/squareup/sample/thingy/BackStackWorkflowImpl.kt

Lines changed: 102 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,31 @@ import kotlinx.coroutines.launch
2323
import kotlinx.coroutines.suspendCancellableCoroutine
2424
import kotlin.coroutines.resume
2525

26+
/**
27+
* Creates a [BackStackWorkflow].
28+
*/
29+
public inline fun <PropsT, OutputT> backStackWorkflow(
30+
crossinline block: suspend RootScope<PropsT, OutputT>.() -> Unit
31+
): Workflow<PropsT, OutputT, BackStackScreen<Screen>> =
32+
object : BackStackWorkflow<PropsT, OutputT>() {
33+
override suspend fun RootScope<PropsT, OutputT>.runBackStack() {
34+
block()
35+
}
36+
}
37+
2638
/**
2739
* Returns a [Workflow] that renders a [BackStackScreen] whose frames are controlled by the code
28-
* in [block].
40+
* in [runBackStack].
2941
*
30-
* [block] can render child workflows by calling [RootScope.showWorkflow]. It can emit outputs to
31-
* its parent by calling [RootScope.emitOutput], and access its props via [RootScope.props].
42+
* [runBackStack] can render child workflows by calling [RootScope.showWorkflow]. It can emit
43+
* outputs to its parent by calling [RootScope.emitOutput], and access its props via
44+
* [RootScope.props].
3245
*
3346
* # Examples
3447
*
3548
* The backstack is represented by _nesting_ `showWorkflow` calls. Consider this example:
3649
* ```
37-
* thingyWorkflow {
50+
* backStackWorkflow {
3851
* showWorkflow(child1) {
3952
* showWorkflow(child2) {
4053
* showWorkflow(child3) {
@@ -51,7 +64,7 @@ import kotlin.coroutines.resume
5164
*
5265
* Contrast with calls in series:
5366
* ```
54-
* thingyWorkflow {
67+
* backStackWorkflow {
5568
* showWorkflow(child1) { finishWith(Unit) }
5669
* showWorkflow(child2) { finishWith(Unit) }
5770
* showWorkflow(child3) { }
@@ -62,7 +75,7 @@ import kotlin.coroutines.resume
6275
*
6376
* These can be combined:
6477
* ```
65-
* thingyWorkflow {
78+
* backStackWorkflow {
6679
* showWorkflow(child1) {
6780
* showWorkflow(child2) {
6881
* // goBack(), or
@@ -79,19 +92,25 @@ import kotlin.coroutines.resume
7992
* `finishWith` to replace itself with `child3`. `child3` can also call `goBack` to show `child`
8093
* again.
8194
*/
82-
public fun <PropsT, OutputT> thingyWorkflow(
83-
block: suspend RootScope<PropsT, OutputT>.() -> Unit
84-
): Workflow<PropsT, OutputT, BackStackScreen<Screen>> = ThingyWorkflow(block)
95+
public abstract class BackStackWorkflow<PropsT, OutputT> :
96+
Workflow<PropsT, OutputT, BackStackScreen<Screen>> {
97+
98+
abstract suspend fun RootScope<PropsT, OutputT>.runBackStack()
99+
100+
final override fun asStatefulWorkflow():
101+
StatefulWorkflow<PropsT, *, OutputT, BackStackScreen<Screen>> =
102+
BackStackWorkflowImpl(this)
103+
}
85104

86105
@DslMarker
87-
annotation class ThingyDsl
106+
annotation class BackStackWorkflowDsl
88107

89-
@ThingyDsl
108+
@BackStackWorkflowDsl
90109
public interface RootScope<PropsT, OutputT> : CoroutineScope {
91110
val props: StateFlow<PropsT>
92111

93112
/**
94-
* Emits an output to the [thingyWorkflow]'s parent.
113+
* Emits an output to the [backStackWorkflow]'s parent.
95114
*/
96115
fun emitOutput(output: OutputT)
97116

@@ -104,7 +123,7 @@ public interface RootScope<PropsT, OutputT> : CoroutineScope {
104123
* When [onOutput] calls [ShowWorkflowScope.finishWith], this workflow stops rendering, its
105124
* rendering is removed from the backstack, and any running output handlers are cancelled.
106125
*
107-
* Note that top-level workflows inside a [thingyWorkflow] cannot call
126+
* Note that top-level workflows inside a [backStackWorkflow] cannot call
108127
* [ShowWorkflowChildScope.goBack] because the parent doesn't necessarily support that operation.
109128
*/
110129
suspend fun <ChildPropsT, ChildOutputT, R> showWorkflow(
@@ -114,11 +133,11 @@ public interface RootScope<PropsT, OutputT> : CoroutineScope {
114133
): R
115134
}
116135

117-
@ThingyDsl
136+
@BackStackWorkflowDsl
118137
public sealed interface ShowWorkflowScope<OutputT, R> : CoroutineScope {
119138

120139
/**
121-
* Emits an output to the [thingyWorkflow]'s parent.
140+
* Emits an output to the [backStackWorkflow]'s parent.
122141
*/
123142
fun emitOutput(output: OutputT)
124143

@@ -147,7 +166,7 @@ public sealed interface ShowWorkflowScope<OutputT, R> : CoroutineScope {
147166
): R
148167
}
149168

150-
@ThingyDsl
169+
@BackStackWorkflowDsl
151170
public sealed interface ShowWorkflowChildScope<OutputT, R> : ShowWorkflowScope<OutputT, R> {
152171
/**
153172
* Removes all workflows started by the parent workflow's handler that invoked this [showWorkflow]
@@ -187,7 +206,7 @@ public suspend inline fun ShowWorkflowScope<*, *>.showWorkflow(
187206

188207
private class RootScopeImpl<PropsT, OutputT>(
189208
override val props: MutableStateFlow<PropsT>,
190-
private val actionSink: Sink<WorkflowAction<PropsT, ThingyState, OutputT>>,
209+
private val actionSink: Sink<WorkflowAction<PropsT, BackStackState, OutputT>>,
191210
coroutineScope: CoroutineScope,
192211
) : RootScope<PropsT, OutputT>, CoroutineScope by coroutineScope {
193212

@@ -210,8 +229,64 @@ private class RootScopeImpl<PropsT, OutputT>(
210229
)
211230
}
212231

232+
private class BackStackWorkflowImpl<PropsT, OutputT>(
233+
private val workflow: BackStackWorkflow<PropsT, OutputT>
234+
) : StatefulWorkflow<
235+
PropsT,
236+
BackStackState,
237+
OutputT,
238+
BackStackScreen<Screen>
239+
>() {
240+
241+
override fun initialState(
242+
props: PropsT,
243+
snapshot: Snapshot?
244+
): BackStackState {
245+
return BackStackState(
246+
stack = emptyList(),
247+
props = MutableStateFlow(props)
248+
)
249+
}
250+
251+
override fun onPropsChanged(
252+
old: PropsT,
253+
new: PropsT,
254+
state: BackStackState
255+
): BackStackState = state.apply {
256+
props.value = new
257+
}
258+
259+
override fun render(
260+
renderProps: PropsT,
261+
renderState: BackStackState,
262+
context: RenderContext<PropsT, BackStackState, OutputT>
263+
): BackStackScreen<Screen> {
264+
context.runningSideEffect("main") {
265+
@Suppress("UNCHECKED_CAST")
266+
val scope = RootScopeImpl(
267+
props = renderState.props as MutableStateFlow<PropsT>,
268+
actionSink = context.actionSink,
269+
coroutineScope = this,
270+
)
271+
with(workflow) {
272+
scope.runBackStack()
273+
}
274+
}
275+
276+
val renderings = renderState.stack.map { frame ->
277+
@Suppress("UNCHECKED_CAST")
278+
(frame as Frame<PropsT, OutputT, *, *, *>).renderWorkflow(context)
279+
}
280+
281+
// TODO show a loading screen if renderings is empty.
282+
return renderings.toBackStackScreen()
283+
}
284+
285+
override fun snapshotState(state: BackStackState): Snapshot? = null
286+
}
287+
213288
private class ShowWorkflowChildScopeImpl<PropsT, OutputT, R>(
214-
private val actionSink: Sink<WorkflowAction<PropsT, ThingyState, OutputT>>,
289+
private val actionSink: Sink<WorkflowAction<PropsT, BackStackState, OutputT>>,
215290
coroutineScope: CoroutineScope,
216291
private val onFinish: (R) -> Unit,
217292
private val thisFrame: Frame<*, *, *, *, *>,
@@ -257,15 +332,15 @@ private class Frame<PropsT, OutputT, ChildPropsT, ChildOutputT, R>(
257332
private val callerJob: Job,
258333
val frameScope: CoroutineScope,
259334
private val onOutput: suspend ShowWorkflowChildScopeImpl<PropsT, OutputT, R>.(ChildOutputT) -> Unit,
260-
private val actionSink: Sink<WorkflowAction<PropsT, ThingyState, OutputT>>,
335+
private val actionSink: Sink<WorkflowAction<PropsT, BackStackState, OutputT>>,
261336
private val parent: Frame<*, *, *, *, *>?,
262337
) {
263338
private val result = CompletableDeferred<R>(parent = frameScope.coroutineContext.job)
264339

265340
suspend fun awaitResult(): R = result.await()
266341

267342
fun renderWorkflow(
268-
context: StatefulWorkflow.RenderContext<PropsT, ThingyState, OutputT>
343+
context: StatefulWorkflow.RenderContext<PropsT, BackStackState, OutputT>
269344
): Screen = context.renderChild(
270345
child = workflow,
271346
props = props,
@@ -294,11 +369,11 @@ private class Frame<PropsT, OutputT, ChildPropsT, ChildOutputT, R>(
294369
})
295370
}
296371

297-
private fun onOutput(output: ChildOutputT): WorkflowAction<PropsT, ThingyState, OutputT> {
372+
private fun onOutput(output: ChildOutputT): WorkflowAction<PropsT, BackStackState, OutputT> {
298373
var canAcceptAction = true
299-
var action: WorkflowAction<PropsT, ThingyState, OutputT>? = null
300-
val sink = object : Sink<WorkflowAction<PropsT, ThingyState, OutputT>> {
301-
override fun send(value: WorkflowAction<PropsT, ThingyState, OutputT>) {
374+
var action: WorkflowAction<PropsT, BackStackState, OutputT>? = null
375+
val sink = object : Sink<WorkflowAction<PropsT, BackStackState, OutputT>> {
376+
override fun send(value: WorkflowAction<PropsT, BackStackState, OutputT>) {
302377
val sendToSink = synchronized(result) {
303378
if (canAcceptAction) {
304379
action = value
@@ -349,7 +424,7 @@ private suspend fun <PropsT, OutputT, ChildPropsT, ChildOutputT, R> showWorkflow
349424
workflow: Workflow<ChildPropsT, ChildOutputT, Screen>,
350425
props: ChildPropsT,
351426
onOutput: suspend ShowWorkflowChildScopeImpl<PropsT, OutputT, R>.(ChildOutputT) -> Unit,
352-
actionSink: Sink<WorkflowAction<PropsT, ThingyState, OutputT>>,
427+
actionSink: Sink<WorkflowAction<PropsT, BackStackState, OutputT>>,
353428
parentFrame: Frame<*, *, *, *, *>?,
354429
): R {
355430
val callerContext = currentCoroutineContext()
@@ -381,12 +456,12 @@ private suspend fun <PropsT, OutputT, ChildPropsT, ChildOutputT, R> showWorkflow
381456
}
382457
}
383458

384-
private class ThingyState(
459+
private class BackStackState(
385460
val stack: List<Frame<*, *, *, *, *>>,
386461
val props: MutableStateFlow<Any?>,
387462
) {
388463

389-
fun copy(stack: List<Frame<*, *, *, *, *>> = this.stack) = ThingyState(
464+
fun copy(stack: List<Frame<*, *, *, *, *>> = this.stack) = BackStackState(
390465
stack = stack,
391466
props = props
392467
)
@@ -395,60 +470,6 @@ private class ThingyState(
395470
fun removeFrame(frame: Frame<*, *, *, *, *>) = copy(stack = stack - frame)
396471
}
397472

398-
private class ThingyWorkflow<PropsT, OutputT>(
399-
private val block: suspend RootScope<PropsT, OutputT>.() -> Unit
400-
) : StatefulWorkflow<
401-
PropsT,
402-
ThingyState,
403-
OutputT,
404-
BackStackScreen<Screen>
405-
>() {
406-
407-
override fun initialState(
408-
props: PropsT,
409-
snapshot: Snapshot?
410-
): ThingyState {
411-
return ThingyState(
412-
stack = emptyList(),
413-
props = MutableStateFlow(props)
414-
)
415-
}
416-
417-
override fun onPropsChanged(
418-
old: PropsT,
419-
new: PropsT,
420-
state: ThingyState
421-
): ThingyState = state.apply {
422-
props.value = new
423-
}
424-
425-
override fun render(
426-
renderProps: PropsT,
427-
renderState: ThingyState,
428-
context: RenderContext<PropsT, ThingyState, OutputT>
429-
): BackStackScreen<Screen> {
430-
context.runningSideEffect("main") {
431-
@Suppress("UNCHECKED_CAST")
432-
val scope = RootScopeImpl(
433-
props = renderState.props as MutableStateFlow<PropsT>,
434-
actionSink = context.actionSink,
435-
coroutineScope = this,
436-
)
437-
block(scope)
438-
}
439-
440-
val renderings = renderState.stack.map { frame ->
441-
@Suppress("UNCHECKED_CAST")
442-
(frame as Frame<PropsT, OutputT, *, *, *>).renderWorkflow(context)
443-
}
444-
445-
// TODO show a loading screen if renderings is empty.
446-
return renderings.toBackStackScreen()
447-
}
448-
449-
override fun snapshotState(state: ThingyState): Snapshot? = null
450-
}
451-
452473
private suspend fun cancelSelf(): Nothing {
453474
val job = currentCoroutineContext().job
454475
job.cancel()

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ fun MyWorkflow(
1616
child2: Workflow<Unit, String, Screen>,
1717
child3: Workflow<String, Nothing, Screen>,
1818
networkCall: suspend (String) -> String
19-
) = thingyWorkflow<String, MyOutputs> {
19+
) = backStackWorkflow<String, MyOutputs> {
2020

2121
// Step 1
2222
showWorkflow(child1) { output ->

0 commit comments

Comments
 (0)