Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -12,6 +12,7 @@ import com.squareup.workflow.RenderContext
import com.squareup.workflow.Snapshot
import com.squareup.workflow.StatefulWorkflow
import com.squareup.workflow.action
import com.squareup.workflow.runningWorker

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

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

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

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
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.squareup.workflow.Worker
import com.squareup.workflow.Workflow
import com.squareup.workflow.action
import com.squareup.workflow.rendering
import com.squareup.workflow.runningWorker
import com.squareup.workflow.stateless
import com.squareup.workflow.testing.launchForTestingFromStartWith
import com.squareup.workflow.ui.WorkflowUiExperimentalApi
Expand Down
6 changes: 1 addition & 5 deletions workflow-core/api/workflow-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,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 @@ -213,12 +211,9 @@ public final class com/squareup/workflow/Workflows {
public static final fun contraMap (Lcom/squareup/workflow/Sink;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow/Sink;
public static final fun getIdentifier (Lcom/squareup/workflow/Workflow;)Lcom/squareup/workflow/WorkflowIdentifier;
public static final fun getWorkflowIdentifier (Lkotlin/reflect/KClass;)Lcom/squareup/workflow/WorkflowIdentifier;
public static final fun impostorWorkflowIdentifier (Lkotlin/reflect/KClass;Lcom/squareup/workflow/WorkflowIdentifier;)Lcom/squareup/workflow/WorkflowIdentifier;
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 +222,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 @@ -73,6 +73,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
}
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 @@ -196,12 +185,62 @@ fun <PropsT, StateT, OutputT> RenderContext<PropsT, StateT, OutputT>.runningWork
worker: Worker<Nothing>,
key: String = ""
) {
// Need to cast to Any so the compiler doesn't complain about unreachable code.
runningWorker(worker as Worker<Any>, key) {
runningWorker(worker, key) {
// The compiler thinks this code is unreachable, and it is correct. But we have to pass a lambda
// here so we might as well check at runtime as well.
@Suppress("UNREACHABLE_CODE", "ThrowableNotThrown")
throw AssertionError("Worker<Nothing> emitted $it")
}
}

/**
* 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).
*
* Like workflows, workers are kept alive across multiple render passes if they're the same type,
* and different workers of distinct types can be run concurrently. However, unlike workflows,
* workers are compared by their _declared_ type, not their actual type. This means that if you
* pass a worker stored in a variable to this function, the type that will be used to compare the
* worker will be the type of the variable, not the type of the object the variable refers to.
*
* @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 <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)
Comment on lines +211 to +218
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't the API I'd ideally like to have, but since we don't have access to the full reflection library in this module, it's the best we can do. If we did, we could create the full worker type from the worker's class and the worker's outputType property, or maybe using the reified T type parameter. But since we don't, all we have is typeOf(), which means we need to reify the entire worker type.

However, while less flexible, it has the nice property that the type that is used to compare the worker will always be exactly the type for the worker that the IDE is showing you when you pass it to runningWorker. There's no mystery or hidden implementation details anymore. You might need to pass a few more keys in, but that's probably a good idea anyway.

}

/**
* 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 +262,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)
18 changes: 11 additions & 7 deletions workflow-core/src/main/java/com/squareup/workflow/Sink.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package com.squareup.workflow

import com.squareup.workflow.WorkflowAction.Updater
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.suspendCancellableCoroutine
Expand Down Expand Up @@ -104,15 +105,18 @@ suspend fun <
action: WorkflowAction<PropsT, StateT, OutputT>
) {
suspendCancellableCoroutine<Unit> { continuation ->
val resumingAction = action<PropsT, StateT, OutputT>({ "sendAndAwaitExecution($action)" }) {
// Don't execute anything if the caller was cancelled while we were in the queue.
if (!continuation.isActive) return@action
val resumingAction = object : WorkflowAction<PropsT, StateT, OutputT> {
override fun toString(): String = "sendAndAwaitApplication($action)"
override fun Updater<PropsT, StateT, OutputT>.apply() {
// Don't execute anything if the caller was cancelled while we were in the queue.
if (!continuation.isActive) return

with(action) {
// Forward our Updater to the real action.
apply()
with(action) {
// Forward our Updater to the real action.
apply()
}
continuation.resume(Unit)
}
continuation.resume(Unit)
}
send(resumingAction)
}
Expand Down
31 changes: 12 additions & 19 deletions workflow-core/src/main/java/com/squareup/workflow/Worker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -154,19 +154,20 @@ interface Worker<out OutputT> {
fun run(): Flow<OutputT>

/**
* Override this method to define equivalence between [Worker]s.
* Override this method to define equivalence between [Worker]s. The default implementation
* returns true if this worker's class is the same as [otherWorker]'s class.
*
* At the end of every render pass, the set of [Worker]s that were requested by the workflow are
* compared to the set from the last render pass using this method. Equivalent workers are allowed
* to keep running. New workers are started ([run] is called and the returned [Flow] is
* collected). Old workers are cancelled by cancelling their collecting coroutines.
* compared to the set from the last render pass using this method. Workers are compared by their
* _declared_ type. Equivalent workers are allowed to keep running. New workers are started ([run]
* is called and the returned [Flow] is collected). Old workers are cancelled by cancelling their
* collecting coroutines. Workers for which [doesSameWorkAs] returns false will also be restarted.
*
* Implementations of this method should not be based on object identity. For example, a [Worker]
* 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 Down Expand Up @@ -384,23 +385,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,
private 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 +408,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