|
1 | 1 | package com.squareup.workflow1 |
2 | 2 |
|
| 3 | +import com.squareup.workflow1.RuntimeConfig.ConflateStaleRenderings |
3 | 4 | import com.squareup.workflow1.internal.WorkflowRunner |
4 | 5 | import com.squareup.workflow1.internal.chained |
5 | 6 | import kotlinx.coroutines.CancellationException |
@@ -101,7 +102,7 @@ import kotlinx.coroutines.launch |
101 | 102 | * A [StateFlow] of [RenderingAndSnapshot]s that will emit any time the root workflow creates a new |
102 | 103 | * rendering. |
103 | 104 | */ |
104 | | -@OptIn(ExperimentalCoroutinesApi::class) |
| 105 | +@OptIn(ExperimentalCoroutinesApi::class, WorkflowExperimentalRuntime::class) |
105 | 106 | public fun <PropsT, OutputT, RenderingT> renderWorkflowIn( |
106 | 107 | workflow: Workflow<PropsT, OutputT, RenderingT>, |
107 | 108 | scope: CoroutineScope, |
@@ -133,21 +134,60 @@ public fun <PropsT, OutputT, RenderingT> renderWorkflowIn( |
133 | 134 | } |
134 | 135 | ) |
135 | 136 |
|
| 137 | + suspend fun <PropsT, OutputT, RenderingT> renderAndEmitOutput( |
| 138 | + runner: WorkflowRunner<PropsT, OutputT, RenderingT>, |
| 139 | + actionResult: ActionProcessingResult?, |
| 140 | + onOutput: suspend (OutputT) -> Unit |
| 141 | + ): RenderingAndSnapshot<RenderingT> { |
| 142 | + val nextRenderAndSnapshot = runner.nextRendering() |
| 143 | + |
| 144 | + when (actionResult) { |
| 145 | + is WorkflowOutput<*> -> { |
| 146 | + @Suppress("UNCHECKED_CAST") |
| 147 | + (actionResult as? WorkflowOutput<OutputT>)?.let { |
| 148 | + onOutput(it.value) |
| 149 | + } |
| 150 | + } |
| 151 | + else -> {} // no -op |
| 152 | + } |
| 153 | + |
| 154 | + return nextRenderAndSnapshot |
| 155 | + } |
| 156 | + |
136 | 157 | scope.launch { |
137 | 158 | while (isActive) { |
138 | | - // It might look weird to start by consuming the output before getting the rendering below, |
| 159 | + lateinit var nextRenderAndSnapshot: RenderingAndSnapshot<RenderingT> |
| 160 | + // It might look weird to start by processing an action before getting the rendering below, |
139 | 161 | // but remember the first render pass already occurred above, before this coroutine was even |
140 | 162 | // launched. |
141 | | - val output: WorkflowOutput<OutputT>? = runner.processAction() |
| 163 | + var actionResult: ActionProcessingResult? = runner.processAction() |
142 | 164 |
|
143 | | - // After resuming from runner.nextOutput() our coroutine could now be cancelled, check so we |
144 | | - // don't surprise anyone with an unexpected rendering pass. Show's over, go home. |
| 165 | + // After resuming from runner.processAction() our coroutine could now be cancelled, check so |
| 166 | + // we don't surprise anyone with an unexpected rendering pass. Show's over, go home. |
145 | 167 | if (!isActive) return@launch |
146 | 168 |
|
147 | | - // After receiving an output, the next render pass must be done before emitting that output, |
148 | | - // so that the workflow states appear consistent to observers of the outputs and renderings. |
149 | | - renderingsAndSnapshots.value = runner.nextRendering() |
150 | | - output?.let { onOutput(it.value) } |
| 169 | + // If the action did produce an Output, we send it immediately after the render pass. |
| 170 | + nextRenderAndSnapshot = renderAndEmitOutput(runner, actionResult, onOutput) |
| 171 | + |
| 172 | + if (runtimeConfig == ConflateStaleRenderings) { |
| 173 | + // With this runtime modification, we do not pass renderings we know to be stale. This |
| 174 | + // means that we may be calling onOutput out of sync with the update of the UI. Output |
| 175 | + // is an event though, and should always occur immediately - i.e. it cannot be stale. |
| 176 | + while (actionResult != ActionsExhausted) { |
| 177 | + // We have more actions we can process, so this rendering is stale. |
| 178 | + actionResult = runner.processAction(waitForAnAction = false) |
| 179 | + |
| 180 | + if (!isActive) return@launch |
| 181 | + |
| 182 | + // If no actions processed, then no new rendering needed. |
| 183 | + if (actionResult == ActionsExhausted) break |
| 184 | + |
| 185 | + nextRenderAndSnapshot = renderAndEmitOutput(runner, actionResult, onOutput) |
| 186 | + } |
| 187 | + } |
| 188 | + |
| 189 | + // Pass on to the UI. |
| 190 | + renderingsAndSnapshots.value = nextRenderAndSnapshot |
151 | 191 | } |
152 | 192 | } |
153 | 193 |
|
|
0 commit comments