Skip to content

Commit

Permalink
WIP Reimplemented Workers using side effects.
Browse files Browse the repository at this point in the history
Issues:
 - Fixes #82, which is the Kotlin half of square/workflow#1021.
 - Fixes #92.
 - Fixes square/workflow#1197.
  • Loading branch information
zach-klippenstein committed Jul 8, 2020
1 parent 5f34282 commit e328ea5
Show file tree
Hide file tree
Showing 49 changed files with 891 additions and 1,265 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import com.squareup.workflow.Snapshot
import com.squareup.workflow.StatefulWorkflow
import com.squareup.workflow.action
import com.squareup.workflow.renderChild
import com.squareup.workflow.runningWorker
import com.squareup.workflow.ui.WorkflowUiExperimentalApi
import com.squareup.workflow.ui.modal.AlertContainerScreen

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import com.squareup.workflow.WorkflowAction
import com.squareup.workflow.WorkflowAction.Companion.noAction
import com.squareup.workflow.WorkflowAction.Updater
import com.squareup.workflow.action
import com.squareup.workflow.runningWorker
import com.squareup.workflow.ui.WorkflowUiExperimentalApi
import com.squareup.workflow.ui.modal.AlertContainerScreen
import com.squareup.workflow.ui.modal.AlertScreen
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import com.squareup.workflow.Snapshot
import com.squareup.workflow.StatefulWorkflow
import com.squareup.workflow.Worker
import com.squareup.workflow.action
import com.squareup.workflow.runningWorker
import com.squareup.workflow.transform
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.transform
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import com.squareup.workflow.StatefulWorkflow
import com.squareup.workflow.Worker
import com.squareup.workflow.action
import com.squareup.workflow.renderChild
import com.squareup.workflow.runningWorker
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import com.squareup.workflow.StatefulWorkflow
import com.squareup.workflow.WorkflowAction
import com.squareup.workflow.WorkflowAction.Updater
import com.squareup.workflow.action
import com.squareup.workflow.runningWorker
import kotlin.time.Duration
import kotlin.time.ExperimentalTime

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import com.squareup.workflow.Snapshot
import com.squareup.workflow.StatefulWorkflow
import com.squareup.workflow.Worker
import com.squareup.workflow.action
import com.squareup.workflow.runningWorker
import kotlinx.coroutines.delay

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import com.squareup.workflow.StatefulWorkflow
import com.squareup.workflow.WorkflowAction
import com.squareup.workflow.action
import com.squareup.workflow.renderChild
import com.squareup.workflow.runningWorker

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import com.squareup.workflow.StatefulWorkflow
import com.squareup.workflow.Workflow
import com.squareup.workflow.WorkflowAction
import com.squareup.workflow.WorkflowAction.Updater
import com.squareup.workflow.runningWorker
import com.squareup.workflow.rx2.asWorker
import com.squareup.workflow.ui.WorkflowUiExperimentalApi
import com.squareup.workflow.ui.backstack.BackStackScreen
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import com.squareup.workflow.StatefulWorkflow
import com.squareup.workflow.Workflow
import com.squareup.workflow.WorkflowAction
import com.squareup.workflow.WorkflowAction.Updater
import com.squareup.workflow.runningWorker
import com.squareup.workflow.rx2.asWorker
import com.squareup.workflow.ui.WorkflowUiExperimentalApi
import com.squareup.workflow.ui.modal.AlertContainerScreen
Expand Down
9 changes: 5 additions & 4 deletions workflow-core/api/workflow-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public final class com/squareup/workflow/ImpostorWorkflow$DefaultImpls {
public abstract class com/squareup/workflow/LifecycleWorker : com/squareup/workflow/Worker {
public fun <init> ()V
public fun doesSameWorkAs (Lcom/squareup/workflow/Worker;)Z
public final fun getOutputType ()Lkotlin/reflect/KType;
public fun onStarted ()V
public fun onStopped ()V
public final fun run ()Lkotlinx/coroutines/flow/Flow;
Expand All @@ -33,14 +34,12 @@ public abstract interface class com/squareup/workflow/RenderContext {
public abstract fun onEvent (Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function1;
public abstract fun renderChild (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
public abstract fun runningSideEffect (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
public abstract fun runningWorker (Lcom/squareup/workflow/Worker;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
}

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

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

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

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

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

public abstract interface class com/squareup/workflow/Workflow {
Expand Down Expand Up @@ -217,8 +219,6 @@ public final class com/squareup/workflow/Workflows {
public static final fun invoke (Lcom/squareup/workflow/EventHandler;)V
public static final fun makeEventSink (Lcom/squareup/workflow/RenderContext;Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow/Sink;
public static final fun mapRendering (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow/Workflow;
public static final fun onWorkerOutput (Lcom/squareup/workflow/RenderContext;Lcom/squareup/workflow/Worker;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
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
public static final fun renderChild (Lcom/squareup/workflow/RenderContext;Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;
public static final fun renderChild (Lcom/squareup/workflow/RenderContext;Lcom/squareup/workflow/Workflow;Ljava/lang/String;)Ljava/lang/Object;
public static final fun renderChild (Lcom/squareup/workflow/RenderContext;Lcom/squareup/workflow/Workflow;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
Expand All @@ -227,6 +227,7 @@ public final class com/squareup/workflow/Workflows {
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;
public static final fun rendering (Lcom/squareup/workflow/Workflow$Companion;Ljava/lang/Object;)Lcom/squareup/workflow/Workflow;
public static final fun runningWorker (Lcom/squareup/workflow/RenderContext;Lcom/squareup/workflow/Worker;Ljava/lang/String;)V
public static final fun runningWorker (Lcom/squareup/workflow/RenderContext;Lcom/squareup/workflow/Worker;Lkotlin/reflect/KType;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
public static synthetic fun runningWorker$default (Lcom/squareup/workflow/RenderContext;Lcom/squareup/workflow/Worker;Ljava/lang/String;ILjava/lang/Object;)V
public static final fun sendAndAwaitApplication (Lcom/squareup/workflow/Sink;Lcom/squareup/workflow/WorkflowAction;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun stateful (Lcom/squareup/workflow/Workflow$Companion;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow/StatefulWorkflow;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package com.squareup.workflow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.reflect.KType

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

final override val outputType: KType? get() = null

/**
* Called when this worker is started. It is executed concurrently with the parent workflow –
* the first render pass that starts this worker *will not* wait for this method to return, and
Expand Down Expand Up @@ -73,6 +76,5 @@ abstract class LifecycleWorker : Worker<Nothing> {
/**
* Equates [LifecycleWorker]s that have the same concrete class.
*/
override fun doesSameWorkAs(otherWorker: Worker<*>): Boolean =
this::class == otherWorker::class
override fun doesSameWorkAs(otherWorker: Worker<*>): Boolean = true
}
61 changes: 46 additions & 15 deletions workflow-core/src/main/java/com/squareup/workflow/RenderContext.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ package com.squareup.workflow

import com.squareup.workflow.WorkflowAction.Companion.noAction
import com.squareup.workflow.WorkflowAction.Updater
import kotlin.reflect.KType
import kotlin.reflect.typeOf

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

/**
* Ensures [worker] is running. When the [Worker] emits an output, [handler] is called
* to determine the [WorkflowAction] to take. When the worker finishes, nothing happens (although
* another render pass may be triggered).
*
* @param key An optional string key that is used to distinguish between identical [Worker]s.
*/
fun <T> runningWorker(
worker: Worker<T>,
key: String = "",
handler: (T) -> WorkflowAction<PropsT, StateT, OutputT>
)

/**
* Ensures [sideEffect] is running with the given [key].
*
Expand Down Expand Up @@ -202,6 +191,48 @@ fun <PropsT, StateT, OutputT> RenderContext<PropsT, StateT, OutputT>.runningWork
}
}

/**
* Ensures [worker] is running. When the [Worker] emits an output, [handler] is called
* to determine the [WorkflowAction] to take. When the worker finishes, nothing happens (although
* another render pass may be triggered).
*
* @param key An optional string key that is used to distinguish between identical [Worker]s.
*/
@OptIn(ExperimentalStdlibApi::class)
/* ktlint-disable parameter-list-wrapping */
inline fun <reified T, reified W : Worker<T>, PropsT, StateT, OutputT>
RenderContext<PropsT, StateT, OutputT>.runningWorker(
worker: W,
key: String = "",
noinline handler: (T) -> WorkflowAction<PropsT, StateT, OutputT>
) {
/* ktlint-enable parameter-list-wrapping */
runningWorker(worker, typeOf<W>(), key, handler)
}

/**
* Ensures [worker] is running. When the [Worker] emits an output, [handler] is called
* to determine the [WorkflowAction] to take. When the worker finishes, nothing happens (although
* another render pass may be triggered).
*
* @param workerType `typeOf<W>()`
* @param key An optional string key that is used to distinguish between identical [Worker]s.
*/
@OptIn(ExperimentalStdlibApi::class)
@PublishedApi
/* ktlint-disable parameter-list-wrapping */
internal fun <T, PropsT, StateT, OutputT>
RenderContext<PropsT, StateT, OutputT>.runningWorker(
worker: Worker<T>,
workerType: KType,
key: String = "",
handler: (T) -> WorkflowAction<PropsT, StateT, OutputT>
) {
/* ktlint-enable parameter-list-wrapping */
val workerWorkflow = WorkerWorkflow<T>(workerType, key)
renderChild(workerWorkflow, props = worker, key = key, handler = handler)
}

/**
* Alternative to [RenderContext.actionSink] that allows externally defined
* event types to be mapped to anonymous [WorkflowAction]s.
Expand All @@ -223,8 +254,8 @@ fun <EventT, PropsT, StateT, OutputT> RenderContext<PropsT, StateT, OutputT>.mak
"Use runningWorker",
ReplaceWith("runningWorker(worker, key, handler)", "com.squareup.workflow.runningWorker")
)
fun <PropsT, StateT, OutputT, T> RenderContext<PropsT, StateT, OutputT>.onWorkerOutput(
inline fun <PropsT, StateT, OutputT, reified T> RenderContext<PropsT, StateT, OutputT>.onWorkerOutput(
worker: Worker<T>,
key: String = "",
handler: (T) -> WorkflowAction<PropsT, StateT, OutputT>
noinline handler: (T) -> WorkflowAction<PropsT, StateT, OutputT>
) = runningWorker(worker, key, handler)
36 changes: 20 additions & 16 deletions workflow-core/src/main/java/com/squareup/workflow/Worker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ import kotlin.reflect.typeOf
* See the documentation on [doesSameWorkAs] for more details on how and when workers are compared
* and the worker lifecycle.
*
* Implementations of this interface that are themselves parameterized on a type should override
* [outputType] to return the [KType] of their type parameter. This allows the runtime to compare
* workers by the output type as well as the concrete type. If [outputType] returns null, all
* workers of the same concrete type will be considered equivalent.
*
* ## Example: Network request
*
* Let's say you have a network service with an API that returns a number, and you want to
Expand Down Expand Up @@ -117,6 +122,14 @@ import kotlin.reflect.typeOf
*/
interface Worker<out OutputT> {

/**
* Should be overridden in subclasses that have their own type parameters, to allow the runtime to
* make use of the value of those type parameters to compare workers. Two workers of the same
* concrete [Worker] class will be considered equivalent if and only if their [outputType]s are
* also equivalent. If this property returns null, the worker will be treated as a `Worker<*>`.
*/
val outputType: KType? get() = null

/**
* Returns a [Flow] to execute the work represented by this worker.
*
Expand Down Expand Up @@ -165,8 +178,7 @@ interface Worker<out OutputT> {
* that performs a network request might check that two workers are requests to the same endpoint
* and have the same request data.
*
* Most implementations of this method will check for concrete type equality, and then match
* on constructor parameters.
* Most implementations of this method should compare constructor parameters.
*
* E.g:
*
Expand All @@ -179,7 +191,7 @@ interface Worker<out OutputT> {
* }
* ```
*/
fun doesSameWorkAs(otherWorker: Worker<*>): Boolean = otherWorker::class == this::class
fun doesSameWorkAs(otherWorker: Worker<*>): Boolean = true

companion object {

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

/**
* A generic [Worker] implementation that defines equivalent workers as those having equivalent
* [type]s. This is used by all the [Worker] builder functions.
* [outputType]s. This is used by all the [Worker] builder functions.
*/
@PublishedApi
internal class TypedWorker<OutputT>(
private val type: KType,
override val outputType: KType,
private val work: Flow<OutputT>
) : Worker<OutputT> {

override fun run(): Flow<OutputT> = work

override fun doesSameWorkAs(otherWorker: Worker<*>): Boolean =
otherWorker is TypedWorker && otherWorker.type == type

override fun toString(): String = "TypedWorker($type)"
override fun toString(): String = "TypedWorker($outputType)"
}

private class TimerWorker(
private data class TimerWorker(
private val delayMs: Long,
private val key: String
) : Worker<Unit> {
Expand All @@ -412,17 +419,14 @@ private class TimerWorker(

override fun doesSameWorkAs(otherWorker: Worker<*>): Boolean =
otherWorker is TimerWorker && otherWorker.key == key

override fun toString(): String = "TimerWorker(delayMs=$delayMs)"
}

private object FinishedWorker : Worker<Nothing> {
override fun run(): Flow<Nothing> = emptyFlow()
override fun doesSameWorkAs(otherWorker: Worker<*>): Boolean = otherWorker === FinishedWorker
override fun toString(): String = "FinishedWorker"
}

private class WorkerWrapper<T, R>(
private data class WorkerWrapper<T, R>(
private val wrapped: Worker<T>,
private val flow: Flow<R>
) : Worker<R> {
Expand Down
Loading

0 comments on commit e328ea5

Please sign in to comment.