Slide 1

Slide 1 text

No content

Slide 2

Slide 2 text

reacting to code sprawl Reactive Workflows Ray Ryan

Slide 3

Slide 3 text

Key-based MVP the legacy

Slide 4

Slide 4 text

Key-based MVP Key flow.set(Key)

Slide 5

Slide 5 text

Key-based MVP Key View

Slide 6

Slide 6 text

Key-based MVP Key Presenter View

Slide 7

Slide 7 text

Key-based MVP Key Presenter View Model Model Model

Slide 8

Slide 8 text

Key-based MVP CartScreen Cart Presenter CartView Item Library Cart

Slide 9

Slide 9 text

Key-based MVP CartScreen Cart Presenter CartView Item Library Cart flow.set(ChargeScreen)

Slide 10

Slide 10 text

ChargeScreen Key-based MVP CartScreen Cart Presenter CartView Item Library Cart

Slide 11

Slide 11 text

Key-based MVP ChargeScreen CartScreen

Slide 12

Slide 12 text

Key-based MVP Charge Presenter Charge View Cart Bill Card Reader ChargeScreen CartScreen

Slide 13

Slide 13 text

Key-based MVP Charge Presenter Charge View Cart Bill Card Reader ChargeScreen CartScreen

Slide 14

Slide 14 text

Key-based MVP Charge Presenter Charge View Cart Bill Card Reader ChargeScreen CartScreen

Slide 15

Slide 15 text

Nav logic sprawl ChargeScreen CartScreen

Slide 16

Slide 16 text

Nav logic sprawl ChargeScreen CartScreen TipScreen

Slide 17

Slide 17 text

Nav logic sprawl ChargeScreen CartScreen TipScreen SignatureScreen

Slide 18

Slide 18 text

Nav logic sprawl ChargeScreen CartScreen TipScreen SignatureScreen

Slide 19

Slide 19 text

Nav logic sprawl ChargeScreen CartScreen TipScreen SignatureScreen ReceiptScreen

Slide 20

Slide 20 text

Nav logic sprawl ChargeScreen CartScreen TipScreen SignatureScreen ReceiptScreen

Slide 21

Slide 21 text

Nav logic sprawl ChargeScreen CartScreen TipScreen SignatureScreen ReceiptScreen

Slide 22

Slide 22 text

Nav logic sprawl ChargeScreen CartScreen TipScreen SignatureScreen ReceiptScreen

Slide 23

Slide 23 text

Nav logic sprawl ChargeScreen CartScreen TipScreen SignatureScreen ReceiptScreen Payment Runner

Slide 24

Slide 24 text

Nav logic sprawl ChargeScreen CartScreen TipScreen SignatureScreen ReceiptScreen Payment Runner SettingsScreen

Slide 25

Slide 25 text

Nav & biz logic entangled Charge Presenter Bill Card Reader ChargeScreen Payment Runner

Slide 26

Slide 26 text

Nav & biz logic entangled Charge Presenter Bill Card Reader ChargeScreen Payment Runner cardSwiped()

Slide 27

Slide 27 text

Nav & biz logic entangled Charge Presenter Bill Card Reader ChargeScreen Payment Runner setCardInfo() cardSwiped()

Slide 28

Slide 28 text

Nav & biz logic entangled Charge Presenter Bill Card Reader ChargeScreen Payment Runner setCardInfo() cardSwiped() next()

Slide 29

Slide 29 text

Nav & biz logic entangled Charge Presenter Bill Card Reader ChargeScreen Payment Runner setCardInfo() cardSwiped() show tip screen? next()

Slide 30

Slide 30 text

show signature screen? Nav & biz logic entangled Charge Presenter Bill Card Reader ChargeScreen Payment Runner setCardInfo() cardSwiped() show tip screen? next()

Slide 31

Slide 31 text

show signature screen? Nav & biz logic entangled Charge Presenter Bill Card Reader ChargeScreen Payment Runner setCardInfo() finishPayment() cardSwiped() show tip screen? next()

Slide 32

Slide 32 text

show signature screen? Nav & biz logic entangled Charge Presenter Bill Card Reader ChargeScreen Payment Runner setCardInfo() finishPayment() cardSwiped() show tip screen? next()

Slide 33

Slide 33 text

AddCustomer Runner Awkward reuse Cart Presenter CartScreen Cart

Slide 34

Slide 34 text

AddCustomer Runner Awkward reuse Cart Presenter start() CartScreen Cart

Slide 35

Slide 35 text

PickCustomerScreen AddCustomer Runner Awkward reuse Cart Presenter start() CartScreen Cart

Slide 36

Slide 36 text

AddCustomer Runner Awkward reuse Pick Customer Presenter PickCustomerScreen CartScreen Cart

Slide 37

Slide 37 text

AddCustomer Runner Awkward reuse Pick Customer Presenter PickCustomerScreen CartScreen Cart setCustomer() + Customer

Slide 38

Slide 38 text

AddCustomer Runner Awkward reuse Receipt Presenter ReceiptScreen Receipt

Slide 39

Slide 39 text

AddCustomer Runner Awkward reuse Receipt Presenter start() ReceiptScreen Receipt flow.set(PickCustomerScreen)

Slide 40

Slide 40 text

AddCustomer Runner Awkward reuse Receipt Presenter start() ReceiptScreen Receipt PickCustomerScreen

Slide 41

Slide 41 text

AddCustomer Runner Awkward reuse Pick Customer Presenter PickCustomerScreen ReceiptScreen

Slide 42

Slide 42 text

AddCustomer Runner Awkward reuse Pick Customer Presenter PickCustomerScreen ReceiptScreen Cart Receipt ?

Slide 43

Slide 43 text

AddCustomer Runner Awkward reuse Pick Customer Presenter PickCustomerScreen ReceiptScreen ? + Customer

Slide 44

Slide 44 text

AddCustomer Runner Awkward reuse Pick Customer Presenter PickCustomerScreen ReceiptScreen ? + Customer

Slide 45

Slide 45 text

Still, pretty good enough Nearly two hundred contributors Around three hundred screens Ships every two weeks

Slide 46

Slide 46 text

Good enough don’t scale four years later

Slide 47

Slide 47 text

Good enough don’t scale 12 minute clean build 2-4 minute rebuild Spaghetti of the commons

Slide 48

Slide 48 text

iOS even worse ViewControllers full of business logic WWAD syndrome • Static singletons • CoreData all the things • Undiffable XIB, Storyboard files

Slide 49

Slide 49 text

Learned helplessness Each platform > 750,000 LOC Remember, THREE HUNDRED SCREENS Nearly half just in payment flow Grassroots heroics no longer practical

Slide 50

Slide 50 text

Learned helplessness Each platform > 750,000 LOC Remember, THREE HUNDRED SCREENS Nearly half just in payment flow Grassroots heroics no longer practical

Slide 51

Slide 51 text

Reclaiming the commons we bring glad tidings from the future

Slide 52

Slide 52 text

Reclaiming the commons Make fixing things someone’s job Make success possible • Define the problems • Describe utopia • Declare and execute two year plan Define the problems

Slide 53

Slide 53 text

Define the problems Edit, compile, debug loop is too slow The code is too complex Dependencies are too interconnected

Slide 54

Slide 54 text

Define the problems Edit, compile, debug loop is too slow The code is too complex Dependencies are too interconnected Modularize: small code blocks build faster Modularize: big features from simpler parts Modularize: dependencies are a DAG

Slide 55

Slide 55 text

Describe Utopia

Slide 56

Slide 56 text

Describe Utopia

Slide 57

Slide 57 text

Utopia's core principals Immutability is assumed Be reactive: push, don’t pull Natural separation of UI and business concerns Uniformity of API across platforms Uniformity Utopia

Slide 58

Slide 58 text

“Uniformity?” Code sharing? Maybe Writing is the easy part, maintenance is forever Shared code is foreign code Have you considered… • Shared tests • Shared API definitions (protocol buffers) Uniformity

Slide 59

Slide 59 text

“Uniformity?” Code sharing? Maybe Writing is the easy part, maintenance is forever Shared code is foreign code Have you considered… • Shared tests • Shared API definitions (protocol buffers) Uniformity

Slide 60

Slide 60 text

Workflows and Bricks Brick

Slide 61

Slide 61 text

Workflows and Bricks Bricks Business model i/o Brick

Slide 62

Slide 62 text

Workflows and Bricks Bricks Workflows Business model i/o App logic UI modeling Brick

Slide 63

Slide 63 text

Workflows and Bricks Bricks Workflows Container Business model i/o App logic UI modeling Rendering UI event handling View Factory Brick

Slide 64

Slide 64 text

Workflows and Bricks Bricks Workflows Container Business model i/o App logic UI modeling Rendering UI event handling Platform neutral* View code View Factory Brick

Slide 65

Slide 65 text

“Brick” rhymes with “schmego” Brick

Slide 66

Slide 66 text

“Brick” rhymes with “schmego”

Slide 67

Slide 67 text

“Brick” rhymes with “schmego”

Slide 68

Slide 68 text

“Brick” rhymes with “schmego” setFoo(FooValue f) Observable bar();

Slide 69

Slide 69 text

“Brick” rhymes with “schmego” setFoo(FooValue f) Observable bar();

Slide 70

Slide 70 text

“Brick” rhymes with “schmego” BarValue getBar(); setFoo(FooValue f) Observable bar();

Slide 71

Slide 71 text

“Brick” rhymes with “schmego” BarValue getBar(); setFoo(FooValue f) Observable bar();

Slide 72

Slide 72 text

“Brick” rhymes with “schmego” BarValue getBar(); setFoo(FooValue f) Observable bar(); SqlBrite / SqlDelight *RxPreferences, *Rooms Retrofit

Slide 73

Slide 73 text

Build big bricks from small bricks

Slide 74

Slide 74 text

Build big bricks from small bricks

Slide 75

Slide 75 text

Build big bricks from small bricks

Slide 76

Slide 76 text

Build big bricks from small bricks j2Objc JavaScript

Slide 77

Slide 77 text

enum class StateOfPlay { PLAYING, VICTORY, DRAW }

Slide 78

Slide 78 text

enum class StateOfPlay { PLAYING, VICTORY, DRAW } enum class MARK { EMPTY, X, O }

Slide 79

Slide 79 text

enum class StateOfPlay { PLAYING, VICTORY, DRAW } enum class MARK { EMPTY, X, O } data class Player( val id: String, val name: String )

Slide 80

Slide 80 text

data class GameState( val id: String, val playerX: Player, val playerO: Player, val stateOfPlay: StateOfPlay, val grid: List>, val activePlayerId: String ) enum class StateOfPlay { PLAYING, VICTORY, DRAW } enum class MARK { EMPTY, X, O } data class Player( val id: String, val name: String )

Slide 81

Slide 81 text

class GameRunner { fun newGame(xPlayerName: String, oPlayerName: String) {} fun restoreGame(clientId: String) {} fun takeSquare(row: Int, col: Int) {} fun end() {} fun gameState(): Observable {} }

Slide 82

Slide 82 text

class GameRunner { /** @throws AssertionError if not called from main thread */ fun newGame(xPlayerName: String, oPlayerName: String) {} /** @throws AssertionError if not called from main thread */ fun restoreGame(clientId: String) {} /** @throws AssertionError if not called from main thread */ fun takeSquare(row: Int, col: Int) {} /** @throws AssertionError if not called from main thread */ fun end() {} fun gameState(): Observable {} } fun newGame(xPlayerName: String, oPlayerName: String) fun restoreGame(clientId: String) class GameRunner {

Slide 83

Slide 83 text

class GameRunner { /** @throws AssertionError if not called from main thread */ fun newGame(xPlayerName: String, oPlayerName: String) {} /** @throws AssertionError if not called from main thread */ fun restoreGame(clientId: String) {} /** @throws AssertionError if not called from main thread */ fun takeSquare(row: Int, col: Int) {} /** @throws AssertionError if not called from main thread */ fun end() {} fun gameState(): Observable {} } fun newGame(xPlayerName: String, oPlayerName: String) fun restoreGame(clientId: String) class GameRunner { observeOn(mainThread()) speakerdeck.com/rjrjr/ where-the-reactive-rubber-meets-the-road

Slide 84

Slide 84 text

class GameRunner { /** @throws AssertionError if not called from main thread */ fun newGame(xPlayerName: String, oPlayerName: String) {} /** @throws AssertionError if not called from main thread */ fun restoreGame(clientId: String) {} /** @throws AssertionError if not called from main thread */ fun takeSquare(row: Int, col: Int) {} /** @throws AssertionError if not called from main thread */ fun end() {} fun gameState(): Observable {} } fun newGame(xPlayerName: String, oPlayerName: String) fun restoreGame(clientId: String) class GameRunner {

Slide 85

Slide 85 text

class GameRunner {

Slide 86

Slide 86 text

class GameRunner { sealed class Command { data class NewGame(val xPlayer: String, val yPlayer: String): Command() data class RestoreGame(val id: String): Command() data class TakeSquare(val row: Int, val col: Int): Command() object End: Command() } fun asTransformer(): Observable.Transformer { } }

Slide 87

Slide 87 text

class GameRunner { sealed class Command { data class NewGame(val xPlayer: String, val yPlayer: String): Command() data class RestoreGame(val id: String): Command() data class TakeSquare(val row: Int, val col: Int): Command() object End: Command() } fun asTransformer(): Observable.Transformer { } } search for: Dan Lew transformer

Slide 88

Slide 88 text

Workflow through the brickyard

Slide 89

Slide 89 text

Remember these things? Charge Presenter Bill ChargeScreen Payment Runner setCardInfo() finishPayment() next() flow.set(TipScreen) Charge View

Slide 90

Slide 90 text

Remember these things? Charge Presenter Bill ChargeScreen Payment Runner setCardInfo() finishPayment() next() flow.set(TipScreen) Charge View

Slide 91

Slide 91 text

Remember these things? Charge Presenter Bill ChargeScreen Payment Runner setCardInfo() finishPayment() next() flow.set(TipScreen) Charge View

Slide 92

Slide 92 text

Remember these things? Charge Presenter Bill ChargeScreen Payment Runner setCardInfo() finishPayment() next() flow.set(TipScreen) Charge View

Slide 93

Slide 93 text

Presenter Runner + = Workflow

Slide 94

Slide 94 text

Workflow as Presenter + = Workflow start(I input) Maybe result() abort() pipeline

Slide 95

Slide 95 text

Workflow as = Workflow start(I input) Maybe result() abort() ui driver Observable screen()

Slide 96

Slide 96 text

Rhymes with “view model” /** Allows interaction with a [Workflow] in a particular state. */ abstract class WorkflowScreen protected constructor( /** Uniquely identifies this screen. */ val key: String, /** Stream of data to render this screen. */ val screenData: Observable, /** Callback methods (click handlers, etc.) handled by this screen. */ val eventHandler: E )

Slide 97

Slide 97 text

LoginScreen AuthorizingScreen SecondFactorScreen

Slide 98

Slide 98 text

class LoginScreen( errorMessage: Observable, eventHandler: Events ) : WorkflowScreen(KEY, errorMessage, eventSink) { companion object { val KEY = LoginScreen::class.name } interface Events { fun onLogin(event: SubmitLogin) } data class SubmitLogin( val email: String, val password: String ) }

Slide 99

Slide 99 text

class LoginScreen( errorMessage: Observable, eventHandler: Events ) : WorkflowScreen(KEY, errorMessage, eventSink) { companion object { val KEY = LoginScreen::class.name } interface Events { fun onLogin(event: SubmitLogin) } data class SubmitLogin( val email: String, val password: String ) }

Slide 100

Slide 100 text

class LoginScreen( errorMessage: Observable, eventHandler: Events ) : WorkflowScreen(KEY, errorMessage, eventSink) { companion object { val KEY = LoginScreen::class.name } interface Events { fun onLogin(event: SubmitLogin) } data class SubmitLogin( val email: String, val password: String ) }

Slide 101

Slide 101 text

class LoginScreen( errorMessage: Observable, eventHandler: Events ) : WorkflowScreen(KEY, errorMessage, eventSink) { companion object { val KEY = LoginScreen::class.name } interface Events { fun onLogin(event: SubmitLogin) } data class SubmitLogin( val email: String, val password: String ) }

Slide 102

Slide 102 text

class ConfirmChargeCardOnFileScreen( screenData: Observable, eventHandler: Events ) : WorkflowScreen(KEY, screenData, eventHandler) { companion object { val KEY = ConfirmChargeCardOnFileScreen::class.name } data class ScreenData( val amountDue: Money, val customerName: String, val cardNameAndNumber: String, val instrumentIndex: Int) interface Events { fun doNotChargeCardOnFile() fun chargeCardOnFile(tenderedAmount: Money, instrumentIndex: Int) }

Slide 103

Slide 103 text

Workflow as = Workflow start(I input) Maybe result() abort() view model source Observable screen()

Slide 104

Slide 104 text

Workflow as = Workflow start(I input) Maybe result() abort() view model source Observable screen() ✔

Slide 105

Slide 105 text

View Factory Container Workflow as = Workflow start(I input) Maybe result() abort() view model source Observable screen() RootView ✔

Slide 106

Slide 106 text

class AuthViewFactory : AbstractViewFactory(asList( bindLayout(LoginScreen.KEY, R.layout.login) { screen -> LoginCoordinator(screen as LoginScreen) }, bindLayout(AuthorizingScreen.KEY, R.layout.authorizing) { screen -> AuthorizingCoordinator(screen as AuthorizingScreen) }, bindLayout(SecondFactorScreen.KEY, R.layout.second_factor) { screen -> SecondFactorCoordinator(screen as SecondFactorScreen) } ))

Slide 107

Slide 107 text

class LoginCoordinator(private val screen: LoginScreen) : Coordinator() { private var subscription: Subscription = Subscriptions.unsubscribed() override fun attach(view: View) {

Slide 108

Slide 108 text

class LoginCoordinator(private val screen: LoginScreen) : Coordinator() { private var subscription: Subscription = Subscriptions.unsubscribed() override fun attach(view: View) { val error = view.findViewById(R.id.login_error_message) as TextView val email = view.findViewById(R.id.login_email) as EditText val password = view.findViewById(R.id.login_password) as EditText val button = view.findViewById(R.id.login_button) as Button

Slide 109

Slide 109 text

class LoginCoordinator(private val screen: LoginScreen) : Coordinator() { private var subscription: Subscription = Subscriptions.unsubscribed() override fun attach(view: View) { val error = view.findViewById(R.id.login_error_message) as TextView val email = view.findViewById(R.id.login_email) as EditText val password = view.findViewById(R.id.login_password) as EditText val button = view.findViewById(R.id.login_button) as Button button.setOnClickListener { _ -> val event = SubmitLogin(email.text.toString(), password.text.toString()) screen.eventHandler.login(event) }

Slide 110

Slide 110 text

class LoginCoordinator(private val screen: LoginScreen) : Coordinator() { private var subscription: Subscription = Subscriptions.unsubscribed() override fun attach(view: View) { val error = view.findViewById(R.id.login_error_message) as TextView val email = view.findViewById(R.id.login_email) as EditText val password = view.findViewById(R.id.login_password) as EditText val button = view.findViewById(R.id.login_button) as Button button.setOnClickListener { _ -> val event = SubmitLogin(email.text.toString(), password.text.toString()) screen.eventHandler.login(event) } subscription = screen.screenData.subscribe { error.text = it } }

Slide 111

Slide 111 text

class LoginCoordinator(private val screen: LoginScreen) : Coordinator() { private var subscription: Subscription = Subscriptions.unsubscribed() override fun attach(view: View) { … } override fun detach(view: View?) { subscription.unsubscribe() }

Slide 112

Slide 112 text

class LoginCoordinator(private val screen: LoginScreen) : Coordinator() { private var subscription: Subscription = Subscriptions.unsubscribed() override fun attach(view: View) { … } override fun detach(view: View?) { subscription.unsubscribe() } search for: square coordinators

Slide 113

Slide 113 text

Workflow as = Workflow start(I input) Maybe result() abort() state machine Container Observable screen() View Factory RootView ✔

Slide 114

Slide 114 text

Workflow as = Workflow start(I input) Maybe result() abort() state machine Container Observable screen() View Factory RootView ✔ ✔ + Square Coordinators

Slide 115

Slide 115 text

Workflow as = Workflow start(I input) Maybe result() abort() state machine Container Observable screen() View Factory RootView ✔ ✔ + Square Coordinators

Slide 116

Slide 116 text

class AuthWorkflow(): Workflow

Slide 117

Slide 117 text

class AuthWorkflow(): Workflow, LoginScreen.Events, SecondFactorScreen.Events

Slide 118

Slide 118 text

class AuthWorkflow(): Workflow, LoginScreen.Events, SecondFactorScreen.Events { private val currentScreen = BehaviorSubject.create()

Slide 119

Slide 119 text

class AuthWorkflow(): Workflow, LoginScreen.Events, SecondFactorScreen.Events { private val currentScreen = BehaviorSubject.create() private val loginMessage = BehaviorSubject.create("") private val authorizingMessage = BehaviorSubject.create() private val secondFactorMessage = BehaviorSubject.create()

Slide 120

Slide 120 text

class AuthWorkflow(): Workflow, LoginScreen.Events, SecondFactorScreen.Events { private val currentScreen = BehaviorSubject.create() private val loginMessage = BehaviorSubject.create("") private val authorizingMessage = BehaviorSubject.create() private val secondFactorMessage = BehaviorSubject.create() override fun screen(): Observable = currentScreen.map { it -> when (it) { LoginScreen.KEY -> LoginScreen(loginMessage, this) AuthorizingScreen.KEY -> AuthorizingScreen(authorizingMessage) SecondFactorScreen.KEY -> SecondFactorScreen(secondFactorMessage, this) else -> throw IllegalArgumentException("Unknown key " + it) } }

Slide 121

Slide 121 text

class AuthWorkflow(): Workflow, LoginScreen.Events, SecondFactorScreen.Events { …

Slide 122

Slide 122 text

class AuthWorkflow(): Workflow, LoginScreen.Events, SecondFactorScreen.Events { … override fun onLogin(event: LoginScreen.SubmitLogin) { stateMachine.onEvent(event) } override fun onSecondFactor(event: SecondFactorScreen.SecondFactor) { stateMachine.onEvent(event) }

Slide 123

Slide 123 text

internal enum class State { LOGIN_PROMPT, AUTHORIZING, SECOND_FACTOR_PROMPT, DONE }

Slide 124

Slide 124 text

internal enum class State { LOGIN_PROMPT, AUTHORIZING, SECOND_FACTOR_PROMPT, DONE } init { stateMachine = FiniteStateMachine( onEntry(AUTHORIZING) { currentScreen.onNext(AuthorizingScreen.KEY) }, onEntry(SECOND_FACTOR_PROMPT) { currentScreen.onNext(SecondFactorScreen.KEY) },

Slide 125

Slide 125 text

internal enum class State { LOGIN_PROMPT, AUTHORIZING, SECOND_FACTOR_PROMPT, DONE } init { stateMachine = FiniteStateMachine( …

Slide 126

Slide 126 text

internal enum class State { LOGIN_PROMPT, AUTHORIZING, SECOND_FACTOR_PROMPT, DONE } init { stateMachine = FiniteStateMachine( … transition(LOGIN_PROMPT, SubmitLogin::class, AUTHORIZING) .doAction { doLogin(it) }, transition(AUTHORIZING, AuthResponse::class, LOGIN_PROMPT) .onlyIf { isLoginFailure(it) } .doAction { response -> val errorMessage = response.errorMessage loginMessage.onNext(errorMessage) },

Slide 127

Slide 127 text

internal enum class State { LOGIN_PROMPT, AUTHORIZING, SECOND_FACTOR_PROMPT, DONE } init { stateMachine = FiniteStateMachine( … transition(LOGIN_PROMPT, SubmitLogin::class, AUTHORIZING) .doAction { doLogin(it) }, transition(AUTHORIZING, AuthResponse::class, LOGIN_PROMPT) .onlyIf { isLoginFailure(it) } .doAction { response -> val errorMessage = response.errorMessage loginMessage.onNext(errorMessage) }, search for: Andy Matuschak states

Slide 128

Slide 128 text

NewGameScreen GamePlayScreen GameOverScreen ConfirmQuitScreen

Slide 129

Slide 129 text

class TicTacToeViewFactory private constructor() : AbstractViewFactory(asList( bindLayout(NewGameScreen.KEY, layout.new_game_layout ) { screen -> NewGameCoordinator(screen as NewGameScreen) }, bindLayout(GamePlayScreen.KEY, layout.game_play_layout ) { screen -> GamePlayCoordinator(screen as GamePlayScreen) }, bindLayout(GameOverScreen.KEY, layout.game_play_layout ) { screen -> GameOverCoordinator(screen as GameOverScreen) }, bindDialog(ConfirmQuitScreen.KEY ) { screen -> ConfirmQuitDialogFactory(screen as ConfirmQuitScreen) } ))

Slide 130

Slide 130 text

class TicTacToeViewFactory private constructor() : AbstractViewFactory(asList( bindLayout(NewGameScreen.KEY, layout.new_game_layout ) { screen -> NewGameCoordinator(screen as NewGameScreen) }, bindLayout(GamePlayScreen.KEY, layout.game_play_layout ) { screen -> GamePlayCoordinator(screen as GamePlayScreen) }, bindLayout(GameOverScreen.KEY, layout.game_play_layout ) { screen -> GameOverCoordinator(screen as GameOverScreen) }, bindDialog(ConfirmQuitScreen.KEY ) { screen -> ConfirmQuitDialogFactory(screen as ConfirmQuitScreen) } ))

Slide 131

Slide 131 text

class TicTacToeViewFactory private constructor() : AbstractViewFactory(asList( bindLayout(NewGameScreen.KEY, layout.new_game_layout ) { screen -> NewGameCoordinator(screen as NewGameScreen) }, bindLayout(GamePlayScreen.KEY, layout.game_play_layout ) { screen -> GamePlayCoordinator(screen as GamePlayScreen) }, bindLayout(GameOverScreen.KEY, layout.game_play_layout ) { screen -> GameOverCoordinator(screen as GameOverScreen) }, bindDialog(ConfirmQuitScreen.KEY ) { screen -> ConfirmQuitDialogFactory(screen as ConfirmQuitScreen) } ))

Slide 132

Slide 132 text

class TicTacToeWorkflow( private val gameRunner: GameRunner ): Workflow

Slide 133

Slide 133 text

class TicTacToeWorkflow( private val gameRunner: GameRunner ): Workflow, NewGameScreen.Events, GamePlayScreen.Events, ConfirmQuitScreen.Events, GameOverScreen.Events {

Slide 134

Slide 134 text

class TicTacToeWorkflow( private val gameRunner: GameRunner ): Workflow, NewGameScreen.Events, GamePlayScreen.Events, ConfirmQuitScreen.Events, GameOverScreen.Events { private val gameStates: Observable = gameRunner.gameState().startWith(NO_GAME)

Slide 135

Slide 135 text

class TicTacToeWorkflow( private val gameRunner: GameRunner ): Workflow, NewGameScreen.Events, GamePlayScreen.Events, ConfirmQuitScreen.Events, GameOverScreen.Events { private val gameStates: Observable = gameRunner.gameState().startWith(NO_GAME) companion object { private val FAKE_ID = UUID.randomUUID().toString() private val NO_GAME = TicTacToeGameState.newGame(FAKE_ID, Player(FAKE_ID, "X"), Player(FAKE_ID, "O")) }

Slide 136

Slide 136 text

class TicTacToeWorkflow( private val gameRunner: GameRunner ): Workflow, NewGameScreen.Events, GamePlayScreen.Events, ConfirmQuitScreen.Events, GameOverScreen.Events { private val gameStates: Observable = gameRunner.gameState().startWith(NO_GAME) …

Slide 137

Slide 137 text

class TicTacToeWorkflow( private val gameRunner: GameRunner ): Workflow, NewGameScreen.Events, GamePlayScreen.Events, ConfirmQuitScreen.Events, GameOverScreen.Events { private val gameStates: Observable = gameRunner.gameState().startWith(NO_GAME) private val quitting = BehaviorSubject.create(false) …

Slide 138

Slide 138 text

class TicTacToeWorkflow( private val gameRunner: GameRunner ): Workflow, NewGameScreen.Events, GamePlayScreen.Events, ConfirmQuitScreen.Events, GameOverScreen.Events { private val gameStates: Observable = gameRunner.gameState().startWith(NO_GAME) private val quitting = BehaviorSubject.create(false) private val screen = combineLatest(gameStates, quitting, { gameState, quitting -> update(gameState, quitting) }) .replay(1) override fun screen(): Observable> = screen …

Slide 139

Slide 139 text

class TicTacToeWorkflow( private val gameRunner: GameRunner ): Workflow, NewGameScreen.Events, GamePlayScreen.Events, ConfirmQuitScreen.Events, GameOverScreen.Events { …

Slide 140

Slide 140 text

class TicTacToeWorkflow( private val gameRunner: GameRunner ): Workflow, NewGameScreen.Events, GamePlayScreen.Events, ConfirmQuitScreen.Events, GameOverScreen.Events { … private fun update(gameState: GameState, quitting: Boolean): WorkflowScreen<*,*> { if (quitting) return ConfirmQuitScreen(this) if (gameState == NO_GAME) NewGameScreen(this) return when (gameState.stateOfPlay) { PLAYING -> GamePlayScreen(gameStates, this) VICTORY, DRAW -> GameOverScreen(gameStates, this) } } …

Slide 141

Slide 141 text

RootView View Factory Container Workflow

Slide 142

Slide 142 text

RootView View Factory Container Workflow

Slide 143

Slide 143 text

RootView Key ScreenData Events WorkflowScreen View Factory Container Workflow

Slide 144

Slide 144 text

RootView Key ScreenData Events WorkflowScreen View Factory Container Workflow

Slide 145

Slide 145 text

RootView Key ScreenData Events WorkflowScreen View Factory Container Workflow

Slide 146

Slide 146 text

RootView View Factory Container Workflow

Slide 147

Slide 147 text

RootView Key ScreenData Events WorkflowScreen View Factory Container Workflow

Slide 148

Slide 148 text

RootView View Factory Container Workflow

Slide 149

Slide 149 text

Workflow You said these things compose

Slide 150

Slide 150 text

Workflow You said these things compose Auth Workflow Game Workflow

Slide 151

Slide 151 text

new CompositeWorkflow<>( // Start in the AuthWorkflow. When it finishes, kick off // a TicTacToe game. new WorkflowBinding<>(AuthWorkflow.class, () -> ignoreStartArg(authWorkflowProvider.get()), (composite, result) -> composite.startWorkflow( forArg(TicTacToeWorkflow.class, (Unit) UNIT))), // When a TicTacToe game ends, start another one. new WorkflowBinding<>(TicTacToeWorkflow.class, () -> ignoreStartArg(ticTacToeWorkflowProvider.get()), (composite, result) -> composite.startWorkflow( forArg(TicTacToeWorkflow.class, (Unit) UNIT))) )

Slide 152

Slide 152 text

new CompositeWorkflow<>( // Start in the AuthWorkflow. When it finishes, kick off // a TicTacToe game. new WorkflowBinding<>(AuthWorkflow.class, () -> ignoreStartArg(authWorkflowProvider.get()), (composite, result) -> composite.startWorkflow( forArg(TicTacToeWorkflow.class, (Unit) UNIT))), // When a TicTacToe game ends, start another one. new WorkflowBinding<>(TicTacToeWorkflow.class, () -> ignoreStartArg(ticTacToeWorkflowProvider.get()), (composite, result) -> composite.startWorkflow( forArg(TicTacToeWorkflow.class, (Unit) UNIT))) )

Slide 153

Slide 153 text

new CompositeWorkflow<>( // Start in the AuthWorkflow. When it finishes, kick off // a TicTacToe game. new WorkflowBinding<>(AuthWorkflow.class, () -> ignoreStartArg(authWorkflowProvider.get()), (composite, result) -> composite.startWorkflow( forArg(TicTacToeWorkflow.class, (Unit) UNIT))), // When a TicTacToe game ends, start another one. new WorkflowBinding<>(TicTacToeWorkflow.class, () -> ignoreStartArg(ticTacToeWorkflowProvider.get()), (composite, result) -> composite.startWorkflow( forArg(TicTacToeWorkflow.class, (Unit) UNIT))) )

Slide 154

Slide 154 text

new CompositeWorkflow<>( // Start in the AuthWorkflow. When it finishes, kick off // a TicTacToe game. new WorkflowBinding<>(AuthWorkflow.class, () -> ignoreStartArg(authWorkflowProvider.get()), (composite, result) -> composite.startWorkflow( forArg(TicTacToeWorkflow.class, (Unit) UNIT))), // When a TicTacToe game ends, start another one. new WorkflowBinding<>(TicTacToeWorkflow.class, () -> ignoreStartArg(ticTacToeWorkflowProvider.get()), (composite, result) -> composite.startWorkflow( forArg(TicTacToeWorkflow.class, (Unit) UNIT))) )

Slide 155

Slide 155 text

new CompositeWorkflow<>( // Start in the AuthWorkflow. When it finishes, kick off // a TicTacToe game. new WorkflowBinding<>(AuthWorkflow.class, () -> ignoreStartArg(authWorkflowProvider.get()), (composite, result) -> composite.startWorkflow( forArg(TicTacToeWorkflow.class, (Unit) UNIT))), // When a TicTacToe game ends, start another one. new WorkflowBinding<>(TicTacToeWorkflow.class, () -> ignoreStartArg(ticTacToeWorkflowProvider.get()), (composite, result) -> composite.startWorkflow( forArg(TicTacToeWorkflow.class, (Unit) UNIT))) )

Slide 156

Slide 156 text

new CompositeWorkflow<>( // Start in the AuthWorkflow. When it finishes, kick off // a TicTacToe game. new WorkflowBinding<>(AuthWorkflow.class, () -> ignoreStartArg(authWorkflowProvider.get()), (composite, result) -> composite.startWorkflow( forArg(TicTacToeWorkflow.class, (Unit) UNIT))), // When a TicTacToe game ends, start another one. new WorkflowBinding<>(TicTacToeWorkflow.class, () -> ignoreStartArg(ticTacToeWorkflowProvider.get()), (composite, result) -> composite.startWorkflow( forArg(TicTacToeWorkflow.class, (Unit) UNIT))) )

Slide 157

Slide 157 text

new CompositeWorkflow<>( // Start in the AuthWorkflow. When it finishes, kick off // a TicTacToe game. new WorkflowBinding<>(AuthWorkflow.class, () -> ignoreStartArg(authWorkflowProvider.get()), (composite, result) -> composite.startWorkflow( forArg(TicTacToeWorkflow.class, (Unit) UNIT))), // When a TicTacToe game ends, start another one. new WorkflowBinding<>(TicTacToeWorkflow.class, () -> ignoreStartArg(ticTacToeWorkflowProvider.get()), (composite, result) -> composite.startWorkflow( forArg(TicTacToeWorkflow.class, (Unit) UNIT))) )

Slide 158

Slide 158 text

Master Status Detail1 Detail2 Detail3

Slide 159

Slide 159 text

Tactics

Slide 160

Slide 160 text

Step zero: make refactoring… …possible • Mock service layer • Robot-based UI tests (espresso, KIF) …tolerable • OkBuck for Android • CocoaPods for iOS

Slide 161

Slide 161 text

Divide first, conquer later

Slide 162

Slide 162 text

Always be writing PSAs Design docs (everyone, all the time) Policy docs (a few, mostly: “write a damn design doc”) How-to guides and sample code

Slide 163

Slide 163 text

Full disclosure Brick pattern established on both First j2Objc brick entering production Non-toy workflows in development on both Composite Workflow on Android Composite WorkflowScreen TBD

Slide 164

Slide 164 text

@rjrjr speakerdeck.com/rjrjr/where-the-reactive-rubber-meets-the-road Dan Lew transformer github.com/square/coordinators Andy Matuschak states (http://bfy.tw/E969) square.com/jobs