legacy-workflow-rx2 / com.squareup.workflow.legacy.rx2 / Reactor

Reactor

interface Reactor<S : Any, E : Any, out O : Any> : Launcher<S, E, O>
Deprecated: Use com.squareup.workflow.Workflow

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:

Handling Inputs

Events

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) }
      }
    }

Singles

onReact 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)
      }

      // ...
    }

Combining Events and Singles

If 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())
        }
      }

      // ...
    }

External Hot Observables

To 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
    }

Nesting Workflows

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.

Functions

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>>

Extension Functions

doLaunch

fun <S : Any, E : Any, O : Any> Reactor<S, E, O>.doLaunch(initialState: S, workflows: WorkflowPool, name: String? = null): Workflow<S, E, O>

Use this to implement WorkflowPool.Launcher.launch.

toCoroutineReactor

fun <S : Any, E : Any, O : Any> Reactor<S, E, O>.toCoroutineReactor(): Reactor<S, E, O>

Adapter to convert a Reactor to a Reactor.