Skip to content

Commit 6c2e14d

Browse files
Reimplemented Workers using side effects.
Issues: - Fixes #82, which is the Kotlin half of square/workflow#1021. - Fixes #92. - Fixes square/workflow#1197.
1 parent 32870bd commit 6c2e14d

File tree

58 files changed

+1299
-1429
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+1299
-1429
lines changed

samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/DungeonAppWorkflow.kt

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import com.squareup.workflow.Snapshot
2626
import com.squareup.workflow.StatefulWorkflow
2727
import com.squareup.workflow.action
2828
import com.squareup.workflow.renderChild
29+
import com.squareup.workflow.runningWorker
2930
import com.squareup.workflow.ui.WorkflowUiExperimentalApi
3031
import com.squareup.workflow.ui.modal.AlertContainerScreen
3132

samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/GameSessionWorkflow.kt

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import com.squareup.workflow.WorkflowAction
3131
import com.squareup.workflow.WorkflowAction.Companion.noAction
3232
import com.squareup.workflow.WorkflowAction.Updater
3333
import com.squareup.workflow.action
34+
import com.squareup.workflow.runningWorker
3435
import com.squareup.workflow.ui.WorkflowUiExperimentalApi
3536
import com.squareup.workflow.ui.modal.AlertContainerScreen
3637
import com.squareup.workflow.ui.modal.AlertScreen

samples/dungeon/common/src/main/java/com/squareup/sample/dungeon/AiWorkflow.kt

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import com.squareup.workflow.Snapshot
2828
import com.squareup.workflow.StatefulWorkflow
2929
import com.squareup.workflow.Worker
3030
import com.squareup.workflow.action
31+
import com.squareup.workflow.runningWorker
3132
import com.squareup.workflow.transform
3233
import kotlinx.coroutines.ExperimentalCoroutinesApi
3334
import kotlinx.coroutines.flow.transform

samples/dungeon/common/src/main/java/com/squareup/sample/dungeon/GameWorkflow.kt

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import com.squareup.workflow.StatefulWorkflow
3636
import com.squareup.workflow.Worker
3737
import com.squareup.workflow.action
3838
import com.squareup.workflow.renderChild
39+
import com.squareup.workflow.runningWorker
3940
import kotlinx.coroutines.delay
4041
import kotlinx.coroutines.flow.Flow
4142
import kotlinx.coroutines.flow.flow

samples/dungeon/timemachine-shakeable/src/main/java/com/squareup/sample/timemachine/shakeable/ShakeableTimeMachineWorkflow.kt

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import com.squareup.workflow.StatefulWorkflow
2828
import com.squareup.workflow.WorkflowAction
2929
import com.squareup.workflow.WorkflowAction.Updater
3030
import com.squareup.workflow.action
31+
import com.squareup.workflow.runningWorker
3132
import kotlin.time.Duration
3233
import kotlin.time.ExperimentalTime
3334

samples/hello-terminal/hello-terminal-app/src/main/java/com/squareup/sample/helloterminal/BlinkingCursorWorkflow.kt

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import com.squareup.workflow.Snapshot
2020
import com.squareup.workflow.StatefulWorkflow
2121
import com.squareup.workflow.Worker
2222
import com.squareup.workflow.action
23+
import com.squareup.workflow.runningWorker
2324
import kotlinx.coroutines.delay
2425

2526
/**

samples/hello-terminal/hello-terminal-app/src/main/java/com/squareup/sample/helloterminal/HelloTerminalWorkflow.kt

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import com.squareup.workflow.StatefulWorkflow
2929
import com.squareup.workflow.WorkflowAction
3030
import com.squareup.workflow.action
3131
import com.squareup.workflow.renderChild
32+
import com.squareup.workflow.runningWorker
3233

3334
private typealias HelloTerminalAction = WorkflowAction<TerminalProps, State, ExitCode>
3435

samples/hello-terminal/todo-terminal-app/src/main/java/com/squareup/sample/hellotodo/EditTextWorkflow.kt

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import com.squareup.workflow.RenderContext
1212
import com.squareup.workflow.Snapshot
1313
import com.squareup.workflow.StatefulWorkflow
1414
import com.squareup.workflow.action
15+
import com.squareup.workflow.runningWorker
1516

1617
class EditTextWorkflow : StatefulWorkflow<EditTextProps, EditTextState, String, String>() {
1718

samples/hello-terminal/todo-terminal-app/src/main/java/com/squareup/sample/hellotodo/TodoWorkflow.kt

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import com.squareup.workflow.Snapshot
3131
import com.squareup.workflow.StatefulWorkflow
3232
import com.squareup.workflow.WorkflowAction
3333
import com.squareup.workflow.action
34+
import com.squareup.workflow.runningWorker
3435

3536
private typealias TodoAction = WorkflowAction<TerminalProps, TodoList, Nothing>
3637

samples/tictactoe/common/src/main/java/com/squareup/sample/authworkflow/AuthWorkflow.kt

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import com.squareup.workflow.StatefulWorkflow
3636
import com.squareup.workflow.Workflow
3737
import com.squareup.workflow.WorkflowAction
3838
import com.squareup.workflow.WorkflowAction.Updater
39+
import com.squareup.workflow.runningWorker
3940
import com.squareup.workflow.rx2.asWorker
4041
import com.squareup.workflow.ui.WorkflowUiExperimentalApi
4142
import com.squareup.workflow.ui.backstack.BackStackScreen

samples/tictactoe/common/src/main/java/com/squareup/sample/gameworkflow/RunGameWorkflow.kt

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import com.squareup.workflow.StatefulWorkflow
4646
import com.squareup.workflow.Workflow
4747
import com.squareup.workflow.WorkflowAction
4848
import com.squareup.workflow.WorkflowAction.Updater
49+
import com.squareup.workflow.runningWorker
4950
import com.squareup.workflow.rx2.asWorker
5051
import com.squareup.workflow.ui.WorkflowUiExperimentalApi
5152
import com.squareup.workflow.ui.modal.AlertContainerScreen

samples/tictactoe/common/src/test/java/com/squareup/sample/mainworkflow/MainWorkflowTest.kt

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import com.squareup.workflow.Worker
1111
import com.squareup.workflow.Workflow
1212
import com.squareup.workflow.action
1313
import com.squareup.workflow.rendering
14+
import com.squareup.workflow.runningWorker
1415
import com.squareup.workflow.stateless
1516
import com.squareup.workflow.testing.launchForTestingFromStartWith
1617
import com.squareup.workflow.ui.WorkflowUiExperimentalApi

workflow-core/api/workflow-core.api

+1-5
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,12 @@ public abstract interface class com/squareup/workflow/RenderContext {
3333
public abstract fun onEvent (Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function1;
3434
public abstract fun renderChild (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
3535
public abstract fun runningSideEffect (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
36-
public abstract fun runningWorker (Lcom/squareup/workflow/Worker;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
3736
}
3837

3938
public final class com/squareup/workflow/RenderContext$DefaultImpls {
4039
public static fun makeActionSink (Lcom/squareup/workflow/RenderContext;)Lcom/squareup/workflow/Sink;
4140
public static fun onEvent (Lcom/squareup/workflow/RenderContext;Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function1;
4241
public static synthetic fun renderChild$default (Lcom/squareup/workflow/RenderContext;Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object;
43-
public static synthetic fun runningWorker$default (Lcom/squareup/workflow/RenderContext;Lcom/squareup/workflow/Worker;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
4442
}
4543

4644
public abstract interface class com/squareup/workflow/Sink {
@@ -213,12 +211,9 @@ public final class com/squareup/workflow/Workflows {
213211
public static final fun contraMap (Lcom/squareup/workflow/Sink;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow/Sink;
214212
public static final fun getIdentifier (Lcom/squareup/workflow/Workflow;)Lcom/squareup/workflow/WorkflowIdentifier;
215213
public static final fun getWorkflowIdentifier (Lkotlin/reflect/KClass;)Lcom/squareup/workflow/WorkflowIdentifier;
216-
public static final fun impostorWorkflowIdentifier (Lkotlin/reflect/KClass;Lcom/squareup/workflow/WorkflowIdentifier;)Lcom/squareup/workflow/WorkflowIdentifier;
217214
public static final fun invoke (Lcom/squareup/workflow/EventHandler;)V
218215
public static final fun makeEventSink (Lcom/squareup/workflow/RenderContext;Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow/Sink;
219216
public static final fun mapRendering (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow/Workflow;
220-
public static final fun onWorkerOutput (Lcom/squareup/workflow/RenderContext;Lcom/squareup/workflow/Worker;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
221-
public static synthetic fun onWorkerOutput$default (Lcom/squareup/workflow/RenderContext;Lcom/squareup/workflow/Worker;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
222217
public static final fun renderChild (Lcom/squareup/workflow/RenderContext;Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;
223218
public static final fun renderChild (Lcom/squareup/workflow/RenderContext;Lcom/squareup/workflow/Workflow;Ljava/lang/String;)Ljava/lang/Object;
224219
public static final fun renderChild (Lcom/squareup/workflow/RenderContext;Lcom/squareup/workflow/Workflow;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
@@ -227,6 +222,7 @@ public final class com/squareup/workflow/Workflows {
227222
public static synthetic fun renderChild$default (Lcom/squareup/workflow/RenderContext;Lcom/squareup/workflow/Workflow;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object;
228223
public static final fun rendering (Lcom/squareup/workflow/Workflow$Companion;Ljava/lang/Object;)Lcom/squareup/workflow/Workflow;
229224
public static final fun runningWorker (Lcom/squareup/workflow/RenderContext;Lcom/squareup/workflow/Worker;Ljava/lang/String;)V
225+
public static final fun runningWorker (Lcom/squareup/workflow/RenderContext;Lcom/squareup/workflow/Worker;Lkotlin/reflect/KType;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
230226
public static synthetic fun runningWorker$default (Lcom/squareup/workflow/RenderContext;Lcom/squareup/workflow/Worker;Ljava/lang/String;ILjava/lang/Object;)V
231227
public static final fun sendAndAwaitApplication (Lcom/squareup/workflow/Sink;Lcom/squareup/workflow/WorkflowAction;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
232228
public static final fun stateful (Lcom/squareup/workflow/Workflow$Companion;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow/StatefulWorkflow;

workflow-core/src/main/java/com/squareup/workflow/LifecycleWorker.kt

+1-2
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,5 @@ abstract class LifecycleWorker : Worker<Nothing> {
7373
/**
7474
* Equates [LifecycleWorker]s that have the same concrete class.
7575
*/
76-
override fun doesSameWorkAs(otherWorker: Worker<*>): Boolean =
77-
this::class == otherWorker::class
76+
override fun doesSameWorkAs(otherWorker: Worker<*>): Boolean = true
7877
}

workflow-core/src/main/java/com/squareup/workflow/RenderContext.kt

+50-17
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ package com.squareup.workflow
2121

2222
import com.squareup.workflow.WorkflowAction.Companion.noAction
2323
import com.squareup.workflow.WorkflowAction.Updater
24+
import kotlin.reflect.KType
25+
import kotlin.reflect.typeOf
2426

2527
/**
2628
* Facilities for a [Workflow] to interact with other [Workflow]s and the outside world from inside
@@ -109,19 +111,6 @@ interface RenderContext<out PropsT, StateT, in OutputT> {
109111
handler: (ChildOutputT) -> WorkflowAction<PropsT, StateT, OutputT>
110112
): ChildRenderingT
111113

112-
/**
113-
* Ensures [worker] is running. When the [Worker] emits an output, [handler] is called
114-
* to determine the [WorkflowAction] to take. When the worker finishes, nothing happens (although
115-
* another render pass may be triggered).
116-
*
117-
* @param key An optional string key that is used to distinguish between identical [Worker]s.
118-
*/
119-
fun <T> runningWorker(
120-
worker: Worker<T>,
121-
key: String = "",
122-
handler: (T) -> WorkflowAction<PropsT, StateT, OutputT>
123-
)
124-
125114
/**
126115
* Ensures [sideEffect] is running with the given [key].
127116
*
@@ -196,12 +185,56 @@ fun <PropsT, StateT, OutputT> RenderContext<PropsT, StateT, OutputT>.runningWork
196185
worker: Worker<Nothing>,
197186
key: String = ""
198187
) {
199-
// Need to cast to Any so the compiler doesn't complain about unreachable code.
200-
runningWorker(worker as Worker<Any>, key) {
188+
runningWorker(worker, key) {
189+
// The compiler thinks this code is unreachable, and it is correct. But we have to pass a lambda
190+
// here so we might as well check at runtime as well.
191+
@Suppress("UNREACHABLE_CODE", "ThrowableNotThrown")
201192
throw AssertionError("Worker<Nothing> emitted $it")
202193
}
203194
}
204195

196+
/**
197+
* Ensures [worker] is running. When the [Worker] emits an output, [handler] is called
198+
* to determine the [WorkflowAction] to take. When the worker finishes, nothing happens (although
199+
* another render pass may be triggered).
200+
*
201+
* @param key An optional string key that is used to distinguish between identical [Worker]s.
202+
*/
203+
@OptIn(ExperimentalStdlibApi::class)
204+
/* ktlint-disable parameter-list-wrapping */
205+
inline fun <T, reified W : Worker<T>, PropsT, StateT, OutputT>
206+
RenderContext<PropsT, StateT, OutputT>.runningWorker(
207+
worker: W,
208+
key: String = "",
209+
noinline handler: (T) -> WorkflowAction<PropsT, StateT, OutputT>
210+
) {
211+
/* ktlint-enable parameter-list-wrapping */
212+
runningWorker(worker, typeOf<W>(), key, handler)
213+
}
214+
215+
/**
216+
* Ensures [worker] is running. When the [Worker] emits an output, [handler] is called
217+
* to determine the [WorkflowAction] to take. When the worker finishes, nothing happens (although
218+
* another render pass may be triggered).
219+
*
220+
* @param workerType `typeOf<W>()`
221+
* @param key An optional string key that is used to distinguish between identical [Worker]s.
222+
*/
223+
@OptIn(ExperimentalStdlibApi::class)
224+
@PublishedApi
225+
/* ktlint-disable parameter-list-wrapping */
226+
internal fun <T, PropsT, StateT, OutputT>
227+
RenderContext<PropsT, StateT, OutputT>.runningWorker(
228+
worker: Worker<T>,
229+
workerType: KType,
230+
key: String = "",
231+
handler: (T) -> WorkflowAction<PropsT, StateT, OutputT>
232+
) {
233+
/* ktlint-enable parameter-list-wrapping */
234+
val workerWorkflow = WorkerWorkflow<T>(workerType, key)
235+
renderChild(workerWorkflow, props = worker, key = key, handler = handler)
236+
}
237+
205238
/**
206239
* Alternative to [RenderContext.actionSink] that allows externally defined
207240
* event types to be mapped to anonymous [WorkflowAction]s.
@@ -223,8 +256,8 @@ fun <EventT, PropsT, StateT, OutputT> RenderContext<PropsT, StateT, OutputT>.mak
223256
"Use runningWorker",
224257
ReplaceWith("runningWorker(worker, key, handler)", "com.squareup.workflow.runningWorker")
225258
)
226-
fun <PropsT, StateT, OutputT, T> RenderContext<PropsT, StateT, OutputT>.onWorkerOutput(
259+
inline fun <PropsT, StateT, OutputT, reified T> RenderContext<PropsT, StateT, OutputT>.onWorkerOutput(
227260
worker: Worker<T>,
228261
key: String = "",
229-
handler: (T) -> WorkflowAction<PropsT, StateT, OutputT>
262+
noinline handler: (T) -> WorkflowAction<PropsT, StateT, OutputT>
230263
) = runningWorker(worker, key, handler)

workflow-core/src/main/java/com/squareup/workflow/Sink.kt

+11-7
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
package com.squareup.workflow
2020

21+
import com.squareup.workflow.WorkflowAction.Updater
2122
import kotlinx.coroutines.flow.Flow
2223
import kotlinx.coroutines.flow.collect
2324
import kotlinx.coroutines.suspendCancellableCoroutine
@@ -104,15 +105,18 @@ suspend fun <
104105
action: WorkflowAction<PropsT, StateT, OutputT>
105106
) {
106107
suspendCancellableCoroutine<Unit> { continuation ->
107-
val resumingAction = action<PropsT, StateT, OutputT>({ "sendAndAwaitExecution($action)" }) {
108-
// Don't execute anything if the caller was cancelled while we were in the queue.
109-
if (!continuation.isActive) return@action
108+
val resumingAction = object : WorkflowAction<PropsT, StateT, OutputT> {
109+
override fun toString(): String = "sendAndAwaitApplication($action)"
110+
override fun Updater<PropsT, StateT, OutputT>.apply() {
111+
// Don't execute anything if the caller was cancelled while we were in the queue.
112+
if (!continuation.isActive) return
110113

111-
with(action) {
112-
// Forward our Updater to the real action.
113-
apply()
114+
with(action) {
115+
// Forward our Updater to the real action.
116+
apply()
117+
}
118+
continuation.resume(Unit)
114119
}
115-
continuation.resume(Unit)
116120
}
117121
send(resumingAction)
118122
}

workflow-core/src/main/java/com/squareup/workflow/Worker.kt

+6-15
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,7 @@ interface Worker<out OutputT> {
165165
* that performs a network request might check that two workers are requests to the same endpoint
166166
* and have the same request data.
167167
*
168-
* Most implementations of this method will check for concrete type equality, and then match
169-
* on constructor parameters.
168+
* Most implementations of this method should compare constructor parameters.
170169
*
171170
* E.g:
172171
*
@@ -384,23 +383,18 @@ fun <T, R> Worker<T>.transform(
384383

385384
/**
386385
* A generic [Worker] implementation that defines equivalent workers as those having equivalent
387-
* [type]s. This is used by all the [Worker] builder functions.
386+
* [outputType]s. This is used by all the [Worker] builder functions.
388387
*/
389388
@PublishedApi
390389
internal class TypedWorker<OutputT>(
391-
private val type: KType,
390+
private val outputType: KType,
392391
private val work: Flow<OutputT>
393392
) : Worker<OutputT> {
394-
395393
override fun run(): Flow<OutputT> = work
396-
397-
override fun doesSameWorkAs(otherWorker: Worker<*>): Boolean =
398-
otherWorker is TypedWorker && otherWorker.type == type
399-
400-
override fun toString(): String = "TypedWorker($type)"
394+
override fun toString(): String = "TypedWorker($outputType)"
401395
}
402396

403-
private class TimerWorker(
397+
private data class TimerWorker(
404398
private val delayMs: Long,
405399
private val key: String
406400
) : Worker<Unit> {
@@ -412,17 +406,14 @@ private class TimerWorker(
412406

413407
override fun doesSameWorkAs(otherWorker: Worker<*>): Boolean =
414408
otherWorker is TimerWorker && otherWorker.key == key
415-
416-
override fun toString(): String = "TimerWorker(delayMs=$delayMs)"
417409
}
418410

419411
private object FinishedWorker : Worker<Nothing> {
420412
override fun run(): Flow<Nothing> = emptyFlow()
421-
override fun doesSameWorkAs(otherWorker: Worker<*>): Boolean = otherWorker === FinishedWorker
422413
override fun toString(): String = "FinishedWorker"
423414
}
424415

425-
private class WorkerWrapper<T, R>(
416+
private data class WorkerWrapper<T, R>(
426417
private val wrapped: Worker<T>,
427418
private val flow: Flow<R>
428419
) : Worker<R> {

0 commit comments

Comments
 (0)