Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Model-View-Intent by Étienne Caron

1b77dd441f657f5aefb3e21283b252e6?s=47 GDG Montreal
September 27, 2017

Model-View-Intent by Étienne Caron

1b77dd441f657f5aefb3e21283b252e6?s=128

GDG Montreal

September 27, 2017
Tweet

Transcript

  1. MVI - Model View Intent

  2. http://hannesdorfmann.com/android/mosby3-mvi-1

  3. https://goo.gl/xAYsZ3

  4. data class Model( val firstName: String = "", val lastName:

    String = "", val score: Int = 0) interface ModelStore { // Intent consumer fun apply(intent: Intent) // Model producer fun model(): Observable<Model> }
  5. https://goo.gl/xAYsZ3

  6. interface View { // Model consumer fun render(model: Model) //

    View Event producer fun viewEvents(): Observable<ViewEvent> } sealed class ViewEvent { data class FirstNameFieldChange(val change: String) : ViewEvent() data class LastNameFieldChange(val change: String) : ViewEvent() object IncrementScoreClick : ViewEvent() }
  7. https://goo.gl/xAYsZ3

  8. sealed class Intent(val reducer: (Model) -> Model) { class IncrementScoreIntent(increment:

    Int = 1) : Intent({ it.copy(score = it.score + increment) }) class EditFirstNameIntent(firstName: String) : Intent({ it.copy(firstName = firstName) }) class EditLastNameIntent(lastName: String) : Intent({ it.copy(lastName = lastName) }) }
  9. sealed class Intent(val reducer: (Model) -> Model) { class IncrementScoreIntent(increment:

    Int = 1) : Intent({ it.copy(score = it.score + increment) }) class EditFirstNameIntent(firstName: String) : Intent({ it.copy(firstName = firstName) }) class EditLastNameIntent(lastName: String) : Intent({ it.copy(lastName = lastName) }) }
  10. sealed class Intent(val reducer: (Model) -> Model) { class IncrementScoreIntent(increment:

    Int = 1) : Intent({ it.copy(score = it.score + increment) }) class EditFirstNameIntent(firstName: String) : Intent({ it.copy(firstName = firstName) }) class EditLastNameIntent(lastName: String) : Intent({ it.copy(lastName = lastName) }) }
  11. sealed class Intent(val reducer: (Model) -> Model) { class IncrementScoreIntent(increment:

    Int = 1) : Intent({ it.copy(score = it.score + increment) }) class EditFirstNameIntent(firstName: String) : Intent({ it.copy(firstName = firstName) }) class EditLastNameIntent(lastName: String) : Intent({ it.copy(lastName = lastName) }) }
  12. https://goo.gl/xAYsZ3

  13. fun Observable<ViewEvent>.toIntent(): Observable<Intent> = map { when (it) { is

    ViewEvent.FirstNameFieldChange -> Intent.EditFirstNameIntent(it.change) is ViewEvent.LastNameFieldChange -> Intent.EditLastNameIntent(it.change) ViewEvent.IncrementScoreClick -> Intent.IncrementScoreIntent() } }
  14. https://goo.gl/xAYsZ3

  15. class ViewImpl() : View { private val viewEvents: PublishRelay<ViewEvent> =

    PublishRelay.create<ViewEvent>() override fun render(model: Model) { println("View.render():\t${model.firstName}, ${model.lastName}: ${model.score} pts.\n") } override fun viewEvents(): Observable<ViewEvent> { return viewEvents.hide() } // Simulates user editing first name fun editFirst(f: String) { println("ViewEvent:\teditFirst( $f )") viewEvents.accept(ViewEvent.FirstNameFieldChange(f)) } // Simulates user editing last name fun editLast(l: String) { println("ViewEvent:\teditLast( $l )") viewEvents.accept(ViewEvent.LastNameFieldChange(l)) } // Simulates user clicking score increment fun buttonClick() { println("ViewEvent:\tbuttonClick()") viewEvents.accept(ViewEvent.IncrementScoreClick) } }
  16. https://goo.gl/xAYsZ3

  17. class ModelStoreImpl : ModelStore { private val intents: PublishRelay<Intent> =

    PublishRelay.create<Intent>() override fun apply(intent: Intent) { intents.accept(intent) } private val store: Observable<Model> = intents .doOnNext { println("Intent:\t\t${it.javaClass.simpleName}") } .map { it.reducer } .scan(Model("Bob", "L'Eponge", 0), { old, reducer -> reducer.invoke(old) }) .doOnNext { println("Model:\t\t${it.firstName}, ${it.lastName}: ${it.score} pts.") } .replay(1) .apply { storeDisposable = connect() } private lateinit var storeDisposable: Disposable override fun model(): Observable<Model> { return store } }
  18. … https://staltz.com/unidirectional-user-interface-architectures.html

  19. … Live Demo