Skip to content

Commit e328ea5

Browse files
WIP 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 5f34282 commit e328ea5

File tree

49 files changed

+891
-1265
lines changed

Some content is hidden

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

49 files changed

+891
-1265
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/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

workflow-core/api/workflow-core.api

+5-4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public final class com/squareup/workflow/ImpostorWorkflow$DefaultImpls {
2222
public abstract class com/squareup/workflow/LifecycleWorker : com/squareup/workflow/Worker {
2323
public fun <init> ()V
2424
public fun doesSameWorkAs (Lcom/squareup/workflow/Worker;)Z
25+
public final fun getOutputType ()Lkotlin/reflect/KType;
2526
public fun onStarted ()V
2627
public fun onStopped ()V
2728
public final fun run ()Lkotlinx/coroutines/flow/Flow;
@@ -33,14 +34,12 @@ public abstract interface class com/squareup/workflow/RenderContext {
3334
public abstract fun onEvent (Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function1;
3435
public abstract fun renderChild (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
3536
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
3737
}
3838

3939
public final class com/squareup/workflow/RenderContext$DefaultImpls {
4040
public static fun makeActionSink (Lcom/squareup/workflow/RenderContext;)Lcom/squareup/workflow/Sink;
4141
public static fun onEvent (Lcom/squareup/workflow/RenderContext;Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function1;
4242
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
4443
}
4544

4645
public abstract interface class com/squareup/workflow/Sink {
@@ -108,13 +107,15 @@ public abstract class com/squareup/workflow/StatelessWorkflow : com/squareup/wor
108107
public final class com/squareup/workflow/TypedWorker : com/squareup/workflow/Worker {
109108
public fun <init> (Lkotlin/reflect/KType;Lkotlinx/coroutines/flow/Flow;)V
110109
public fun doesSameWorkAs (Lcom/squareup/workflow/Worker;)Z
110+
public fun getOutputType ()Lkotlin/reflect/KType;
111111
public fun run ()Lkotlinx/coroutines/flow/Flow;
112112
public fun toString ()Ljava/lang/String;
113113
}
114114

115115
public abstract interface class com/squareup/workflow/Worker {
116116
public static final field Companion Lcom/squareup/workflow/Worker$Companion;
117117
public abstract fun doesSameWorkAs (Lcom/squareup/workflow/Worker;)Z
118+
public abstract fun getOutputType ()Lkotlin/reflect/KType;
118119
public abstract fun run ()Lkotlinx/coroutines/flow/Flow;
119120
}
120121

@@ -127,6 +128,7 @@ public final class com/squareup/workflow/Worker$Companion {
127128

128129
public final class com/squareup/workflow/Worker$DefaultImpls {
129130
public static fun doesSameWorkAs (Lcom/squareup/workflow/Worker;Lcom/squareup/workflow/Worker;)Z
131+
public static fun getOutputType (Lcom/squareup/workflow/Worker;)Lkotlin/reflect/KType;
130132
}
131133

132134
public abstract interface class com/squareup/workflow/Workflow {
@@ -217,8 +219,6 @@ public final class com/squareup/workflow/Workflows {
217219
public static final fun invoke (Lcom/squareup/workflow/EventHandler;)V
218220
public static final fun makeEventSink (Lcom/squareup/workflow/RenderContext;Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow/Sink;
219221
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
222222
public static final fun renderChild (Lcom/squareup/workflow/RenderContext;Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;
223223
public static final fun renderChild (Lcom/squareup/workflow/RenderContext;Lcom/squareup/workflow/Workflow;Ljava/lang/String;)Ljava/lang/Object;
224224
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 +227,7 @@ public final class com/squareup/workflow/Workflows {
227227
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;
228228
public static final fun rendering (Lcom/squareup/workflow/Workflow$Companion;Ljava/lang/Object;)Lcom/squareup/workflow/Workflow;
229229
public static final fun runningWorker (Lcom/squareup/workflow/RenderContext;Lcom/squareup/workflow/Worker;Ljava/lang/String;)V
230+
public static final fun runningWorker (Lcom/squareup/workflow/RenderContext;Lcom/squareup/workflow/Worker;Lkotlin/reflect/KType;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
230231
public static synthetic fun runningWorker$default (Lcom/squareup/workflow/RenderContext;Lcom/squareup/workflow/Worker;Ljava/lang/String;ILjava/lang/Object;)V
231232
public static final fun sendAndAwaitApplication (Lcom/squareup/workflow/Sink;Lcom/squareup/workflow/WorkflowAction;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
232233
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

+4-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ package com.squareup.workflow
2121
import kotlinx.coroutines.flow.Flow
2222
import kotlinx.coroutines.flow.flow
2323
import kotlinx.coroutines.suspendCancellableCoroutine
24+
import kotlin.reflect.KType
2425

2526
/**
2627
* [Worker] that performs some action when the worker is started and/or stopped.
@@ -30,6 +31,8 @@ import kotlinx.coroutines.suspendCancellableCoroutine
3031
*/
3132
abstract class LifecycleWorker : Worker<Nothing> {
3233

34+
final override val outputType: KType? get() = null
35+
3336
/**
3437
* Called when this worker is started. It is executed concurrently with the parent workflow –
3538
* the first render pass that starts this worker *will not* wait for this method to return, and
@@ -73,6 +76,5 @@ abstract class LifecycleWorker : Worker<Nothing> {
7376
/**
7477
* Equates [LifecycleWorker]s that have the same concrete class.
7578
*/
76-
override fun doesSameWorkAs(otherWorker: Worker<*>): Boolean =
77-
this::class == otherWorker::class
79+
override fun doesSameWorkAs(otherWorker: Worker<*>): Boolean = true
7880
}

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

+46-15
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
*
@@ -202,6 +191,48 @@ fun <PropsT, StateT, OutputT> RenderContext<PropsT, StateT, OutputT>.runningWork
202191
}
203192
}
204193

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

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

+20-16
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ import kotlin.reflect.typeOf
5252
* See the documentation on [doesSameWorkAs] for more details on how and when workers are compared
5353
* and the worker lifecycle.
5454
*
55+
* Implementations of this interface that are themselves parameterized on a type should override
56+
* [outputType] to return the [KType] of their type parameter. This allows the runtime to compare
57+
* workers by the output type as well as the concrete type. If [outputType] returns null, all
58+
* workers of the same concrete type will be considered equivalent.
59+
*
5560
* ## Example: Network request
5661
*
5762
* Let's say you have a network service with an API that returns a number, and you want to
@@ -117,6 +122,14 @@ import kotlin.reflect.typeOf
117122
*/
118123
interface Worker<out OutputT> {
119124

125+
/**
126+
* Should be overridden in subclasses that have their own type parameters, to allow the runtime to
127+
* make use of the value of those type parameters to compare workers. Two workers of the same
128+
* concrete [Worker] class will be considered equivalent if and only if their [outputType]s are
129+
* also equivalent. If this property returns null, the worker will be treated as a `Worker<*>`.
130+
*/
131+
val outputType: KType? get() = null
132+
120133
/**
121134
* Returns a [Flow] to execute the work represented by this worker.
122135
*
@@ -165,8 +178,7 @@ interface Worker<out OutputT> {
165178
* that performs a network request might check that two workers are requests to the same endpoint
166179
* and have the same request data.
167180
*
168-
* Most implementations of this method will check for concrete type equality, and then match
169-
* on constructor parameters.
181+
* Most implementations of this method should compare constructor parameters.
170182
*
171183
* E.g:
172184
*
@@ -179,7 +191,7 @@ interface Worker<out OutputT> {
179191
* }
180192
* ```
181193
*/
182-
fun doesSameWorkAs(otherWorker: Worker<*>): Boolean = otherWorker::class == this::class
194+
fun doesSameWorkAs(otherWorker: Worker<*>): Boolean = true
183195

184196
companion object {
185197

@@ -384,23 +396,18 @@ fun <T, R> Worker<T>.transform(
384396

385397
/**
386398
* A generic [Worker] implementation that defines equivalent workers as those having equivalent
387-
* [type]s. This is used by all the [Worker] builder functions.
399+
* [outputType]s. This is used by all the [Worker] builder functions.
388400
*/
389401
@PublishedApi
390402
internal class TypedWorker<OutputT>(
391-
private val type: KType,
403+
override val outputType: KType,
392404
private val work: Flow<OutputT>
393405
) : Worker<OutputT> {
394-
395406
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)"
407+
override fun toString(): String = "TypedWorker($outputType)"
401408
}
402409

403-
private class TimerWorker(
410+
private data class TimerWorker(
404411
private val delayMs: Long,
405412
private val key: String
406413
) : Worker<Unit> {
@@ -412,17 +419,14 @@ private class TimerWorker(
412419

413420
override fun doesSameWorkAs(otherWorker: Worker<*>): Boolean =
414421
otherWorker is TimerWorker && otherWorker.key == key
415-
416-
override fun toString(): String = "TimerWorker(delayMs=$delayMs)"
417422
}
418423

419424
private object FinishedWorker : Worker<Nothing> {
420425
override fun run(): Flow<Nothing> = emptyFlow()
421-
override fun doesSameWorkAs(otherWorker: Worker<*>): Boolean = otherWorker === FinishedWorker
422426
override fun toString(): String = "FinishedWorker"
423427
}
424428

425-
private class WorkerWrapper<T, R>(
429+
private data class WorkerWrapper<T, R>(
426430
private val wrapped: Worker<T>,
427431
private val flow: Flow<R>
428432
) : Worker<R> {

0 commit comments

Comments
 (0)