interface Reactor<S : Any, E : Any, out O : Any> : Launcher<S, E, O>An Rx2 adapter for com.squareup.workflow.Reactor, allowing implementors to ignore that interface's reliance on kotlin.coroutines.
A Reactor is a factory for Workflows implemented as state machines that:
When a new workflow is launched, each consecutive state will be passed to onReact, along with an EventChannel that can be used to accept events, and a WorkflowPool that can be used to delegate work to nested workflows.
onReact returns a Single (read "Future") that eventually emits a Reaction indicating what to do next, one of:
To handle events received from Workflow.sendEvent, call events.select
from within your onReact method:
override fun onReact(
state: MyState,
events: EventChannel<MyEvent>,
workflows: WorkflowPool
): Single<Reaction<MyState, MyResult>> = when(state) {
FooOrDone -> events.select(state) {
onEvent<Foo> { handleFoo() }
onEvent<Done> { FinishWith(it.result) }
}
FooOrBarState -> events.select(state) {
onEvent<Foo> { handleFoo() }
onEvent<Bar> { EnterState(FooOrDone) }
}
}
SinglesonReact is not limited to using the given EventChannel to calculate its next state. For example, a service call might be handled this way, mapping a Single generated by Retrofit to the appropriate Reaction.
override fun onReact(
state: MyState,
events: EventChannel<MyEvent>,
workflows: WorkflowPool
): Single<Reaction<MyState, MyResult>> = when(state) {
WaitingForStatusResponse -> statusService.update().map { response ->
if (response.success) EnterState(ShowingStatus(response))
else EnterState(ShowingFailure(response)
}
// ...
}
SinglesIf you need to mix such command-like Singles with workflow events, make a
Worker for your Single using singleWorker.
EventChannel offers onWorkerResult in addition to
onEvent. Remember to call WorkflowPool.abandonWorkflow if you leave while the Worker is
still running!
private val updateWorker = singleWorker { statusService.update }
override fun onReact(
state: MyState,
events: EventChannel<MyEvent>,
workflows: WorkflowPool
): Single<Reaction<MyState, MyResult>> = when(state) {
WaitingForStatusResponse -> events.select {
workflows.onWorkerResult(updateWorker) { response ->
if (response.success) EnterState(ShowingStatus(response))
else EnterState(ShowingFailure(response)
}
onEvent<Cancel> {
workflows.abandonWorker(updateWorker)
EnterState(ShowingFailure(Canceled())
}
}
// ...
}
ObservablesTo monitor external hot observables, which might fire at any time, subscribe to them in the launch method, and map their values to events passed to Workflow.sendEvent. Use Workflow.toCompletable to tear down those subscriptions when the workflow ends.
override fun launch(
initialState: MyState,
workflows: WorkflowPool
) : Workflow<MyState, MyEvent, MyResult> {
val workflow = doLaunch(initialState, workflows)
val subs = CompositeSubscription()
subs += connectivityMonitor.connectivity.subscribe {
workflow.sendEvent(ConnectivityUpdate(it))
}
subs += workflow.toCompletable().subscribe { subs.clear() }
return workflow
}
To define a state that delegates to a nested workflow, have the S subtype that represents it implement com.squareup.workflow.Delegating. Use onNextDelegateReaction when entering that state to drive the nested workflow and react to its result.
For example, in the simplest case, where the parent workflow accepts no events of its own while the delegate is running, the delegating state type would look like this:
data class RunningNested(
// Initial state of the nested workflow, and updated as the
override val delegateState: NestedState = NestedState.start()
) : MyState(), Delegating<NestedState, NestedEvent, NestedResult> {
override val id = NestedReactor::class.makeWorkflowId()
}
You'd register a NestedReactor instance with the WorkflowPool passed to your launch
implementation:
class MyReactor(
private val nestedReactor : NestedReactor
) {
override fun launch(
initialState: MyState,
workflows: WorkflowPool
) : Workflow<MyState, MyEvent, MyResult> {
workflows.register(nestedReactor)
return doLaunch(initialState, workflows)
}
and in your onReact method, use onNextDelegateReaction to wait for the nested workflow to do its job:
is Delegating -> events.select {
workflows.onNextDelegateReaction(state) {
when (it) {
is EnterState -> EnterState(state.copy(delegateState = it.state))
is FinishWith -> when (it.result) {
is DoSomething -> EnterState(DoingSomething)
is DoSomethingElse -> EnterState(DoingSomethingElse)
}
}
}
}
If you need to handle other events while the workflow is running, remember to call WorkflowPool.abandonWorkflow if you leave while the nested workflow is still running!
is Delegating -> events.select {
workflows.onNextDelegateReaction(state) {
when (it) {
is EnterState -> EnterState(state.copy(delegateState = it.state))
if FinishWith -> when (it.result) {
is DoSomething -> EnterState(DoingSomething)
is DoSomethingElse -> EnterState(DoingSomethingElse)
}
}
}
onEvent<Cancel> {
workflows.abandonDelegate(state.id)
EnterState(NeverMind)
}
}
To accept events for nested workflows, e.g. to drive a UI, define com.squareup.workflow.Renderers for both S and each of its Delegating subtypes. WorkflowPool.input can be used by renderers to route events to any running workflow.
launch |
open fun launch(initialState: S, workflows: WorkflowPool): Workflow<S, E, O> |
onReact |
abstract fun onReact(state: S, events: EventChannel<E>, workflows: WorkflowPool): Single<out Reaction<S, O>> |
doLaunch |
Use this to implement WorkflowPool.Launcher.launch. fun <S : Any, E : Any, O : Any> Reactor<S, E, O>. |
toCoroutineReactor |
Adapter to convert a Reactor to a Reactor. fun <S : Any, E : Any, O : Any> Reactor<S, E, O>. |