Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

Reactive Workflows Update

rjrjr
August 28, 2018

Reactive Workflows Update

rjrjr

August 28, 2018
Tweet

More Decks by rjrjr

Other Decks in Technology

Transcript

  1. Immutability is assumed Be reactive: push, don’t pull Natural separation

    of UI and business concerns Composition or go home Cardinal Architecture Virtues
  2. Business Objects CRUD State View Model Event Update Workflow Navigation,

    View Model NewGame PlayGame QuitGame GameOver Container Rendering, Event Handling
  3. val bar: BarValue fun setFoo(f: FooValue) fun setBar(b: BarValue) fun

    setBaz(b: Bazvalue) val bang: Observable<BangValue> val buzz: Observable<BuzzValue>
  4. fun perform(op: Operation) sealed class Operation { class Update(vararg stuff:

    Stuff) : Operation() class Delete(vararg ids: Int) : Operation() } val stuffs: Observable<List<Stuff>>
  5. Container Rendering, Event Handling Business Objects CRUD Workflow Navigation, View

    Model NewGame PlayGame QuitGame GameOver State View Model Event Update
  6. Play Game Playing( Symbol ) Completed( Ending ) Symbol {

    X, O, None } Ending { Victor(Symbol) Draw Quitter(Symbol) }
  7. Play Game Playing( Symbol ) Completed( Ending ) Event {

    TakeSquare(row, col) Quit } Symbol { X, O, None } Ending { Victor(Symbol) Draw Quitter(Symbol) }
  8. Play Game Playing( Turn ) Completed( Ending, Turn ) typealias

    Board = List<List<Symbol>> data class Turn( val players: Map<Symbol, String>, val playing: Symbol = X, val board: Board = EMPTY_BOARD )
  9. interface Workflow<out S : Any, out O : Any> {

    val state: Observable<out S> val result: Single<out O> fun abandon() }
  10. class PlayGameWorkflow(initial: State) : Workflow<Turn, Completed> { sealed class Event

    { class TakeSquare( val row: Int, val col: Int ) : Event() object Quit : Event() } private val events = PublishRelay.create<Event>() fun sendEvent(e: Event) = events.call(e)
  11. class PlayGameWorkflow(initial: State) : Workflow<Turn, Completed> { sealed class Event

    { class TakeSquare( val row: Int, val col: Int ) : Event() object Quit : Event() } private val events = PublishRelay.create<Event>() fun sendEvent(e: Event) = events.call(e)
  12. class PlayGameWorkflow(initial: State) : Workflow<Turn, Completed> { sealed class State

    { abstract val turn: Turn data class Playing( override val turn: Turn ) : State() data class Completed( val ending: Ending, override val turn: Turn ) : State() } data class Turn( val players: Map<Symbol, String>, val playing: Symbol = X, val board: Board = EMPTY_BOARD )
  13. class PlayGameWorkflow(initial: State) : Workflow<Turn, Completed> { sealed class State

    { abstract val turn: Turn data class Playing( override val turn: Turn ) : State() data class Completed( val ending: Ending, override val turn: Turn ) : State() }
  14. class PlayGameWorkflow(initial: State) : Workflow<Turn, Completed> { sealed class State

    { abstract val turn: Turn data class Playing( override val turn: Turn ) : State() data class Completed( val ending: Ending, override val turn: Turn ) : State() } sealed class Ending { object Victor : Ending() object Draw : Ending() object Quitter : Ending() }
  15. class PlayGameWorkflow(initial: State) : Workflow<Turn, Completed> { private val states

    = BehaviorRelay.create(Single.just(initial)) private val _state: ConnectableObservable<State> = states .switchMap { it.toObservable() } .doOnNext { currentState -> states.call(nextState(currentState)) } .replay(1) private fun nextState(fromState: State): Single<State> = when (fromState) { is Completed -> Observable.never<State>() is Playing -> events.first().map { event -> when (event) { is TakeSquare -> takeSquareAndNextState(fromState.turn, event.row, event.col) Quit -> Completed(Quitter, fromState.turn) } } }.toSingle()
  16. class PlayGameWorkflow(initial: State) : Workflow<Turn, Completed> { private val states

    = BehaviorRelay.create(Single.just(initial)) private val _state: ConnectableObservable<State> = states .switchMap { it.toObservable() } .doOnNext { currentState -> states.call(nextState(currentState)) } .replay(1) private fun nextState(fromState: State): Single<State> = when (fromState) { is Completed -> Observable.never<State>() is Playing -> events.first().map { event -> when (event) { is TakeSquare -> takeSquareAndNextState(fromState.turn, event.row, event.col) Quit -> Completed(Quitter, fromState.turn) } } }.toSingle()
  17. class PlayGameWorkflow(initial: State) : Workflow<Turn, Completed> { private val states

    = BehaviorRelay.create(Single.just(initial)) private val _state: ConnectableObservable<State> = states .switchMap { it.toObservable() } .doOnNext { currentState -> states.call(nextState(currentState)) } .replay(1) private fun nextState(fromState: State): Single<State> = when (fromState) { is Completed -> Observable.never<State>() is Playing -> events.first().map { event -> when (event) { is TakeSquare -> takeSquareAndNextState(fromState.turn, event.row, event.col) Quit -> Completed(Quitter, fromState.turn) } } }.toSingle()
  18. class PlayGameWorkflow(initial: State) : Workflow<Turn, Completed> { private val states

    = BehaviorRelay.create(Single.just(initial)) private val _state: ConnectableObservable<State> = states .switchMap { it.toObservable() } .doOnNext { currentState -> states.call(nextState(currentState)) } .replay(1) private fun nextState(fromState: State): Single<State> = when (fromState) { is Completed -> Observable.never<State>() is Playing -> events.first().map { event -> when (event) { is TakeSquare -> takeSquareAndNextState(fromState.turn, event.row, event.col) Quit -> Completed(Quitter, fromState.turn) } } }.toSingle()
  19. class PlayGameWorkflow(initial: State) : Workflow<Turn, Completed> { private val states

    = BehaviorRelay.create(Single.just(initial)) private val _state: ConnectableObservable<State> = states .switchMap { it.toObservable() } .doOnNext { currentState -> states.call(nextState(currentState)) } .replay(1) private fun nextState(fromState: State): Single<State> = when (fromState) { is Completed -> Observable.never<State>() is Playing -> events.first().map { event -> when (event) { is TakeSquare -> takeSquareAndNextState(fromState.turn, event.row, event.col) Quit -> Completed(Quitter, fromState.turn) } } }.toSingle()
  20. class PlayGameWorkflow(initial: State) : Workflow<Turn, Completed> { private val states

    = BehaviorRelay.create(Single.just(initial)) private val _state: ConnectableObservable<State> = states .switchMap { it.toObservable() } .doOnNext { currentState -> states.call(nextState(currentState)) } .replay(1) private fun nextState(fromState: State): Single<State> = when (fromState) { is Completed -> Observable.never<State>() is Playing -> events.first().map { event -> when (event) { is TakeSquare -> takeSquareAndNextState(fromState.turn, event.row, event.col) Quit -> Completed(Quitter, fromState.turn) } } }.toSingle()
  21. class PlayGameWorkflow(initial: State) : Workflow<Turn, Completed> { private val states

    = BehaviorRelay.create(Single.just(initial)) private val _state: ConnectableObservable<State> = states .switchMap { it.toObservable() } .doOnNext { currentState -> states.call(nextState(currentState)) } .replay(1) private fun nextState(fromState: State): Single<State> = when (fromState) { is Completed -> Observable.never<State>() is Playing -> events.first().map { event -> when (event) { is TakeSquare -> takeSquareAndNextState(fromState.turn, event.row, event.col) Quit -> Completed(Quitter, fromState.turn) } } }.toSingle()
  22. class PlayGameWorkflow(initial: State) : Workflow<Turn, Completed> { private val states

    = BehaviorRelay.create(Single.just(initial)) private val _state: ConnectableObservable<State> = states .switchMap { it.toObservable() } .doOnNext { currentState -> states.call(nextState(currentState)) } .replay(1) override val state: Observable<out Turn> = _state.takeWhile { it !is Completed } .map { it.turn } override val result: Single<out Completed> = _state.ofType<Completed>() .first() .toSingle() .cache()
  23. class PlayGameWorkflow(initial: State) : Workflow<Turn, Completed> { private fun nextState(fromState:

    State): Single<State> = when (fromState) { is Completed -> Observable.never<State>() is Playing -> events.first().map { event -> when (event) { is TakeSquare -> takeSquareAndNextState(fromState.turn, event.row, event.col) Quit -> Completed(Quitter, fromState.turn) } } }.toSingle()
  24. class PlayGameReactor : Reactor<Playing, Event, Completed>() { override fun onReact(fromState:

    Playing): Single<out Reaction<Playing, Completed>> = selectEvent { onEvent<TakeSquare> { EnterState(takeSquareAndNextState(fromState.turn, it.row, it.col)) } onEvent<Quit> { FinishWith(Completed(Quitter, fromState.turn)) } }
  25. class PlayGameReactor : Reactor<Playing, Event, Completed>() { override fun onReact(fromState:

    Playing): Single<out Reaction<Playing, Completed>> = selectEvent { onEvent<TakeSquare> { EnterState(takeSquareAndNextState(fromState.turn, it.row, it.col)) } onEvent<Quit> { FinishWith(Completed(Quitter, fromState.turn)) } }
  26. class PlayGameReactor : Reactor<Playing, Event, Completed>() { override fun onReact(fromState:

    Playing): Single<out Reaction<Playing, Completed>> = selectEvent { onEvent<TakeSquare> { EnterState(takeSquareAndNextState(fromState.turn, it.row, it.col)) } onEvent<Quit> { FinishWith(Completed(Quitter, fromState.turn)) } }
  27. class PlayGameReactor : Reactor<Playing, Event, Completed>() { override fun onReact(fromState:

    Playing): Single<out Reaction<Playing, Completed>> = selectEvent { onEvent<TakeSquare> { EnterState(takeSquareAndNextState(fromState.turn, it.row, it.col)) } onEvent<Quit> { FinishWith(Completed(Quitter, fromState.turn)) } }
  28. class PlayGameReactor : Reactor<Playing, Event, Completed>() { typealias PlayGameWorkflow =

    ReactorWorkflow<Playing, Event, Completed> companion object { fun startWorkflow( x: String, o: String ): PlayGameWorkflow = PlayGameReactor().asWorkflow(Playing(Turn(x, o))) }
  29. class PlayGameReactor : Reactor<Playing, Event, Completed>() { typealias PlayGameWorkflow =

    ReactorWorkflow<Playing, Event, Completed> companion object { fun startWorkflow( x: String, o: String ): PlayGameWorkflow = PlayGameReactor().asWorkflow(Playing(Turn(x, o))) fun resumeWorkflow(turn: Turn): PlayGameWorkflow = PlayGameReactor().asWorkflow(Playing(turn)) }
  30. class GameAppReactor : Reactor<State, Event, Unit>() { sealed class State

    { class NewGame( val defaultXName: String = "X", val defaultOName: String = "O" ) : State() class Playing(val workflow: PlayGameWorkflow) : State() class MaybeQuitting(val completedGame: Completed) : State() class GameOver(val completedGame: Completed) : State() }
  31. class GameAppReactor : Reactor<State, Event, Unit>() { sealed class Event

    { class StartGame( val x: String, val o: String ) : Event() object ConfirmQuit : Event() object ContinuePlaying : Event() object StartNewGame : Event() object QuitApp : Event() }
  32. class GameAppReactor : Reactor<State, Event, Unit>() { override fun onReact(state:

    State): Single<out Reaction<State, Unit>> = when (state) {
  33. class GameAppReactor : Reactor<State, Event, Unit>() { override fun onReact(state:

    State): Single<out Reaction<State, Unit>> = when (state) { is NewGame -> selectEvent { onEvent<QuitApp> { FinishWith(Unit) } onEvent<StartGame> { EnterState(Playing(PlayGameReactor.startWorkflow(it.x, it.o))) } }
  34. class GameAppReactor : Reactor<State, Event, Unit>() { override fun onReact(state:

    State): Single<out Reaction<State, Unit>> = when (state) { is NewGame -> selectEvent { onEvent<QuitApp> { FinishWith(Unit) } onEvent<StartGame> { EnterState(Playing(PlayGameReactor.startWorkflow(it.x, it.o))) } }
  35. class GameAppReactor : Reactor<State, Event, Unit>() { override fun onReact(state:

    State): Single<out Reaction<State, Unit>> = when (state) { is NewGame -> selectEvent { onEvent<QuitApp> { FinishWith(Unit) } onEvent<StartGame> { EnterState(Playing(PlayGameReactor.startWorkflow(it.x, it.o))) } } is Playing -> state.workflow.result.map { result -> when (result.ending) { Quitter -> EnterState(MaybeQuitting(result)) else -> EnterState(GameOver(result)) } }
  36. class GameAppReactor : Reactor<State, Event, Unit>() { override fun onReact(state:

    State): Single<out Reaction<State, Unit>> = when (state) { is Playing -> state.workflow.result.map { result -> when (result.ending) { Quitter -> EnterState(MaybeQuitting(result)) else -> EnterState(GameOver(result)) } } is MaybeQuitting -> selectEvent { onEvent<ConfirmQuit> { EnterState(GameOver(state.completedGame)) } onEvent<ContinuePlaying> { EnterState(Playing(PlayGameReactor.resumeWorkflow(state.completedGame.turn))) } }
  37. class GameAppReactor : Reactor<State, Event, Unit>() { override fun onReact(state:

    State): Single<out Reaction<State, Unit>> = when (state) { is Playing -> state.workflow.result.map { result -> when (result.ending) { Quitter -> EnterState(MaybeQuitting(result)) else -> EnterState(GameOver(result)) } } is MaybeQuitting -> selectEvent { onEvent<ConfirmQuit> { EnterState(GameOver(state.completedGame)) } onEvent<ContinuePlaying> { EnterState(Playing(PlayGameReactor.resumeWorkflow(state.completedGame.turn))) } }
  38. class GameAppReactor : Reactor<State, Event, Unit>() { override fun onReact(state:

    State): Single<out Reaction<State, Unit>> = when (state) { is Playing -> state.workflow.result.map { result -> when (result.ending) { Quitter -> EnterState(MaybeQuitting(result)) else -> EnterState(GameOver(result)) } } is MaybeQuitting -> selectEvent { onEvent<ConfirmQuit> { EnterState(GameOver(state.completedGame)) } onEvent<ContinuePlaying> { EnterState(Playing(PlayGameReactor.resumeWorkflow(state.completedGame.turn))) } }
  39. override fun onReact(state: State): Single<out Reaction<State, Unit>> = when (state)

    { // Left as an exercise for the reader. // // Hint: Retrofit requests are rx.Single<*>
  40. Container Rendering, Event Handling Business Objects CRUD Workflow Navigation, View

    Model NewGame PlayGame QuitGame GameOver State View Model Event Update
  41. /** * @param data A complete rendering of this screen.

    * @param eventHandler The object that accepts events for this screen. */ class Screen<D : Any, E : Any>( val data: D, val eventHandler: E )
  42. /** * @param data A complete rendering of this screen.

    * @param eventHandler The object that accepts events for this screen. */ class Screen<D : Any, E : Any>( val data: D, val eventHandler: E ) typealias AnyScreen = Screen<*, *>
  43. /** * @param data A complete rendering of this screen.

    * @param eventHandler The object that accepts events for this screen. */ class Screen<D : Any, E : Any>( val data: D, val eventHandler: E ) typealias AnyScreen = Screen<*, *> /** * A [Workflow] that exposes its state as a stream * of [screen models][Screen]. */ typealias ScreenWorkflow<O> = Workflow<AnyScreen, O>
  44. class GameAppScreens(private val wrapped: GameAppWorkflow) : ScreenWorkflow<Unit> { override val

    state: Observable<out AnyScreen> = wrapped.state.switchMap { when (it) { is Playing -> it.workflow.state.map { playing -> Screen(playing.turn, it.workflow) } else -> just(Screen(it, wrapped)) } } override val result: Single<out Unit> = wrapped.result override fun abandon() = wrapped.abandon() }
  45. class GameAppScreens(private val wrapped: GameAppWorkflow) : ScreenWorkflow<Unit> { override val

    state: Observable<out AnyScreen> = wrapped.state.switchMap { when (it) { is Playing -> it.workflow.state.map { playing -> Screen(playing.turn, it.workflow) } else -> just(Screen(it, wrapped)) } } override val result: Single<out Unit> = wrapped.result override fun abandon() = wrapped.abandon() }
  46. class GameAppScreens(private val wrapped: GameAppWorkflow) : ScreenWorkflow<Unit> { override val

    state: Observable<out AnyScreen> = wrapped.state.switchMap { when (it) { is Playing -> it.workflow.state else -> just(Screen(it, wrapped)) } } override val result: Single<out Unit> = wrapped.result override fun abandon() = wrapped.abandon() }
  47. public struct ViewRegistry<View> { // ... public func provideView<T: Screen>(for

    screen: T) -> View // ... } The View Registry turns screen models into live views.
  48. public struct ViewRegistry<View> { // ... public func provideView<T: Screen>(for

    screen: T) -> View // Registers a factory block for the screen type `T`. public mutating func register<T: Screen>( screenType: T.Type, factory: @escaping (Observable<T>, ViewRegistry<View>) -> View // ... } The View Registry requires screens to be registered. 73
  49. public struct ViewRegistry<View> { // ... public func provideView<T: Screen>(for

    screen: T) -> View // Registers a factory block for the screen type `T`. public mutating func register<T: Screen>( screenType: T.Type, factory: @escaping (Observable<T>, ViewRegistry<View>) -> View // ... } The View Registry is recursive. 74
  50. public struct BackStackScreen: Screen { public var currentItem: Item public

    struct Item { public var screen: Screen public var key: String public var barContent: NavigationBarContent } }
  51. 90

  52. 91

  53. 92

  54. 94

  55. 96

  56. 98