Save 37% off PRO during our Black Friday Sale! »

Reactive Workflows Update

82ee6bce819efe5b9cc6c51dea03e8da?s=47 rjrjr
August 28, 2018

Reactive Workflows Update

82ee6bce819efe5b9cc6c51dea03e8da?s=128

rjrjr

August 28, 2018
Tweet

Transcript

  1. Ray Ryan Timothy Donnelly Reactive Workflows Update

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

    of UI and business concerns Composition or go home Cardinal Architecture Virtues
  3. None
  4. Workflow Business Objects Container

  5. Workflow Container Business Objects CRUD

  6. Workflow Navigation, View Model NewGame PlayGame QuitGame GameOver Business Objects

    CRUD Container
  7. Container Rendering, Event Handling Business Objects CRUD Workflow Navigation, View

    Model NewGame PlayGame QuitGame GameOver
  8. Business Objects CRUD State View Model Event Update Workflow Navigation,

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

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

    Stuff) : Operation() class Delete(vararg ids: Int) : Operation() } val stuffs: Observable<List<Stuff>>
  11. fun perform(op: Operation) val stuffs: Observable<List<Stuff>> RxPreferences, Rooms SqlDelight Retrofit

  12. Container Rendering, Event Handling Business Objects CRUD Workflow Navigation, View

    Model NewGame PlayGame QuitGame GameOver State View Model Event Update
  13. NewGame PlayGame QuitGame GameOver

  14. New Game Screen Play Game Screen Quit Game Screen Game

    Over Screen
  15. New Game Play Game Quit Game Game Over

  16. Play Game X’s Turn Draw X Wins Quit O Wins

    O’s Turn
  17. Play Game Playing( X or O ) Completed( X, O,

    draw or quit )
  18. Play Game Playing( Symbol ) Completed( Ending ) Symbol {

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

    TakeSquare(row, col) Quit } Symbol { X, O, None } Ending { Victor(Symbol) Draw Quitter(Symbol) }
  20. 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 )
  21. interface Workflow<out S : Any, out O : Any> {

    val state: Observable<out S> val result: Single<out O> fun abandon() }
  22. 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)
  23. 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)
  24. 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 )
  25. 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() }
  26. 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() }
  27. 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()
  28. 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()
  29. 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()
  30. 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()
  31. 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()
  32. 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()
  33. 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()
  34. 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()
  35. 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()
  36. 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)) } }
  37. 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)) } }
  38. 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)) } }
  39. 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)) } }
  40. 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))) }
  41. 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)) }
  42. New Game Quit Game Game Over Play Game

  43. New Game PlayGameWorkflow Quit Game Game Over

  44. 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() }
  45. 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() }
  46. class GameAppReactor : Reactor<State, Event, Unit>() { override fun onReact(state:

    State): Single<out Reaction<State, Unit>> = when (state) {
  47. 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))) } }
  48. 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))) } }
  49. 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)) } }
  50. 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))) } }
  51. 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))) } }
  52. 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))) } }
  53. New Game PlayGameWorkflow Quit Game Game Over

  54. Playing(PlayGameWorkflow) NewGame GameOver(Completed) MaybeQuitting(Completed)

  55. Game Service Playing(PlayGameWorkflow) NewGame MaybeQuitting(Completed) ConfirmQuit Continue Playing Quitter !Quitter

    StartNewGame GameOver(Completed) StartNewGame
  56. Game Service Playing(PlayGameWorkflow) NewGame GameOver( Completed, SyncState ) MaybeQuitting(Completed) ConfirmQuit

    Continue Playing Quitter !Quitter StartNewGame StartNewGame
  57. 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<*>
  58. Container Rendering, Event Handling Business Objects CRUD Workflow Navigation, View

    Model NewGame PlayGame QuitGame GameOver State View Model Event Update
  59. Workflow Navigation, View Model Container Rendering, Event Handling Business Objects

    CRUD ?
  60. /** * @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 )
  61. /** * @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<*, *>
  62. /** * @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>
  63. 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() }
  64. 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() }
  65. 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() }
  66. Tim Time

  67. Workflows + Swift

  68. None
  69. None
  70. None
  71. None
  72. public struct ViewRegistry<View> { // ... public func provideView<T: Screen>(for

    screen: T) -> View // ... } The View Registry turns screen models into live views.
  73. 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
  74. 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
  75. None
  76. None
  77. None
  78. 78 Adapting UIKit to a reactive world

  79. 79 Back Stack

  80. public struct BackStackScreen: Screen { public var currentItem: Item public

    struct Item { public var screen: Screen public var key: String public var barContent: NavigationBarContent } }
  81. None
  82. None
  83. None
  84. None
  85. None
  86. None
  87. None
  88. 88 Modal Container

  89. public struct ModalContainerScreen: Screen { public var main: Screen public

    var modal: Screen? } 89
  90. 90

  91. 91

  92. 92

  93. 93 Demo

  94. 94

  95. 95 What else can we do with a fully decoupled

    UI layer?
  96. 96

  97. 97 ...and what about debugging tools?

  98. 98

  99. square.com Ray Ryan / octodon.social/@rjrjr Timothy Donnelly / twitter.com/tdonnelly github.com/JakeWharton/RxRelay

    github.com/square/coordinators