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

Simple MVI Architecture for Android

Simple MVI Architecture for Android

An introduction to the Model View Intent architecture pattern.

This pattern takes ideas and concepts from Reactive and Functional programming. We'll go over the core principles of this pattern, and how you can apply them in your Android applications.

We'll look at concrete implementation details, and explore some of its benefits. Some familiarity with Kotlin and RxJava is assumed.

2f2f5eed0536a76382a121b1b8a88c89?s=128

Etienne Caron

April 09, 2019
Tweet

Transcript

  1. Simple MVI Architecture for Android

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

  3. None
  4. https://xkcd.com/1312

  5. None
  6. Simple MVI Architecture for Android

  7. Simple MVI Architecture for Android

  8. output input input output user device

  9. output input input output user device

  10. output input input output user device

  11. output input input output user device

  12. input output input output user device

  13. input output input output user device

  14. Model Intent View

  15. Intent View

  16. Intent View

  17. Intent View

  18. Intent View

  19. Intent View

  20. Intent View

  21. View MainViewEvent Intent sealed class MainViewEvent {_ object ThumbsUpClick :_MainViewEvent()

    object LoveItClick :_MainViewEvent() }_
  22. MainViewEvent Intent sealed class MainViewEvent {_ object ThumbsUpClick :_MainViewEvent() object

    LoveItClick :_MainViewEvent() }_ View
  23. View sealed class MainViewEvent {_ object ThumbsUpClick :_MainViewEvent() object LoveItClick

    :_MainViewEvent() }_ MainViewEvent ThumbsUpClick LoveItClick + +❤
  24. View sealed class MainViewEvent {_ object ThumbsUpClick :_MainViewEvent() object LoveItClick

    :_MainViewEvent() }_ MainViewEvent ThumbsUpClick LoveItClick + +❤ ❤
  25. View sealed class MainViewEvent {_ object ThumbsUpClick :_MainViewEvent() object LoveItClick

    :_MainViewEvent() }_ MainViewEvent ThumbsUpClick LoveItClick + +❤ ❤ ❤
  26. View sealed class MainViewEvent {_ object ThumbsUpClick :_MainViewEvent() object LoveItClick

    :_MainViewEvent() }_ MainViewEvent ThumbsUpClick LoveItClick + +❤ ❤ ❤
  27. ❤ ❤ View.clicks():Observable<Unit> View package com.jakewharton.rxbinding2.view Observable<MainViewEvent>

  28. View thumbButton.clicks() package com.jakewharton.rxbinding2.view Observable<MainViewEvent> ❤ ❤

  29. View thumbButton.clicks() map { MainViewEvent.ThumbsUpClick } package com.jakewharton.rxbinding2.view Observable<MainViewEvent> ❤

  30. View thumbButton.clicks() map { MainViewEvent.ThumbsUpClick } package com.jakewharton.rxbinding2.view Observable<MainViewEvent> ❤

  31. View heartButton.clicks() package com.jakewharton.rxbinding2.view Observable<MainViewEvent> ❤ ❤

  32. View heartButton.clicks() map { MainViewEvent.LoveItClick } package com.jakewharton.rxbinding2.view Observable<MainViewEvent> ❤

  33. View heartButton.clicks() map { MainViewEvent.LoveItClick } ❤ package com.jakewharton.rxbinding2.view Observable<MainViewEvent>

    ❤ ❤
  34. View heartButton.clicks() map { MainViewEvent.LoveItClick } ❤ ❤ package com.jakewharton.rxbinding2.view

    Observable<MainViewEvent> ❤ ❤
  35. Observable<MainViewEvent> View ❤ ❤

  36. Observable.merge( heartButton.clicks().map {_LoveItClick }, thumbButton.clicks().map {_ThumbsUpClick_} ) Observable<MainViewEvent> View ❤

  37. View ❤ ❤ class MainActivity {_ override fun viewEvents(): Observable<MainViewEvent>_{

    return Observable.merge( heartButton.clicks().map {_LoveItClick }, thumbButton.clicks().map {_ThumbsUpClick_} )_ }_ }_
  38. /** * This allows us to group all the viewEvents

    from * one view in a single Observable. */ interface ViewEventObservable<E>_{ funxviewEvents():xObservable<E> }x class MainActivity :_ViewEventObservable<MainViewEvent> {_ override fun viewEvents(): Observable<MainViewEvent>_{ return Observable.merge( heartButton.clicks().map {_LoveItClick }, thumbButton.clicks().map {_ThumbsUpClick_} )_ }_ }_ View ❤ ❤
  39. class MainActivity :_ViewEventObservable<MainViewEvent> {_ private val disposables = CompositeDisposable() override

    fun onResume()_{ super.onResume() disposables += viewEvents().subscribe(1 )1 }1 override fun onPause()_{ super.onPause() disposables.clear() }2 override fun viewEvents(): Observable<MainViewEvent>_{ return Observable.merge( heartButton.clicks().map {_LoveItClick }, thumbButton.clicks().map {_ThumbsUpClick_} )_ }_ }_ View Intent
  40. class MainActivity :_ViewEventObservable<MainViewEvent> {_ private val disposables = CompositeDisposable() override

    fun onResume()_{ super.onResume() disposables += viewEvents().subscribe(1 )1 }1 override fun onPause()_{ super.onPause() disposables.clear() }2 override fun viewEvents(): Observable<MainViewEvent>_{ return Observable.merge( heartButton.clicks().map {_LoveItClick }, thumbButton.clicks().map {_ThumbsUpClick_} )_ }_ }_ View Intent ❓ ❓
  41. class MainActivity :_ViewEventObservable<MainViewEvent> {_ private val disposables = CompositeDisposable() override

    fun onResume()_{ super.onResume() disposables += viewEvents().subscribe(1 MainViewIntentFactory::process )1 }1 override fun onPause()_{ super.onPause() disposables.clear() }2 override fun viewEvents(): Observable<MainViewEvent>_{ return Observable.merge( heartButton.clicks().map {_LoveItClick }, thumbButton.clicks().map {_ThumbsUpClick_} )_ }_ }_ View x Intent
  42. ModelState store x Intent View Model M

  43. ModelState store x Intent View ❓ Model M

  44. ModelState store x Intent View ❓ Model M

  45. ModelState store Model M val ❤:Int val :Int M

  46. ModelState store Model M val ❤:Int val :Int data class

    UpvoteModel(val hearts:Int, val thumbs:Int) M (Don’t mind me. My turn is coming up soon.)
  47. ModelState store Intent x View Model M data class UpvoteModel(val

    hearts:Int, val thumbs:Int) val ❤ :Int val :Int x x
  48. ModelState store Intent x View Model M data class UpvoteModel(val

    hearts:Int, val thumbs:Int) newState = oldState.copy(thumbs = thumbs + 1) // 0, 1 val ❤ :Int val :Int x x
  49. ModelState store Intent x View Model M data class UpvoteModel(val

    hearts:Int, val thumbs:Int) newState = oldState.copy(thumbs = thumbs + 1) // 0, 1 newState = newState.copy(hearts = hearts + 1) // 1, 1 val ❤ :Int val :Int x x
  50. ModelState store Intent x View Model M data class UpvoteModel(val

    hearts:Int, val thumbs:Int) newState = oldState.copy(thumbs = thumbs + 1) // 0, 1 newState = newState.copy(hearts = hearts + 1) // 1, 1 newState = newState.copy(hearts = hearts + 1) // 2, 1 val ❤ :Int val :Int x x
  51. ModelState store Intent x View Model M data class UpvoteModel(val

    hearts:Int, val thumbs:Int) newState = oldState.copy(thumbs = thumbs + 1) // 0, 1 newState = newState.copy(hearts = hearts + 1) // 1, 1 newState = newState.copy(hearts = hearts + 1) // 2, 1 val ❤ :Int val :Int x x ❓
  52. ModelState store Intent x View Model M data class UpvoteModel(val

    hearts:Int, val thumbs:Int) interface Intent<T> {x fun reduce(oldState: T): T }x val ❤ :Int val :Int x x
  53. ModelState store Intent x View Model M data class UpvoteModel(val

    hearts:Int, val thumbs:Int) interface Intent<T> {x fun reduce(oldState: T): T }x class AddHeart():Intent<UpvoteModel> {y override fun reduce(oldState: UpvoteModel)_= oldState.copy(hearts =_oldState.hearts_+ 1) }y val ❤ :Int val :Int x x
  54. ModelState store Intent x View Model M data class UpvoteModel(val

    hearts:Int, val thumbs:Int) fun_toIntent(viewEvent: MainViewEvent):Intent<UpvoteModel>_{ return when (viewEvent) {z MainViewEvent.LoveItClick ->_AddHeart() MainViewEvent.ThumbsUpClick ->_AddThumb() }z }z2 val ❤ :Int val :Int x x
  55. interface IntentFactory<E> { fun_process(viewEvent:E) } object MainViewIntentFactory : IntentFactory<MainViewEvent> {

    override fun_process(viewEvent: MainViewEvent)_{ UpvoteModelStore.process(toIntent(viewEvent)) }x private fun_toIntent(viewEvent: MainViewEvent):Intent<UpvoteModel>_{ return when (viewEvent) {z MainViewEvent.LoveItClick ->_AddHeart() MainViewEvent.ThumbsUpClick ->_AddThumb() }z }z2 } Intent x View
  56. Intent x interface_ModelStore<S> { fun process(intent: Intent<S>) fun modelState(): Observable<S>

    } override fun_process(viewEvent: MainViewEvent)_{ UpvoteModelStore.process(toIntent(viewEvent)) }x ModelStore RxModelStore UpvoteModelStore (Don’t mind me. My turn is coming up soon too.) Model M val ❤ :Int val :Int ModelState store + + +
  57. Intent x interface_ModelStore<S>_{ fun process(intent: Intent<S>) fun modelState(): Observable<S> }x

    ModelStore RxModelStore UpvoteModelStore (Don’t mind me. My turn is coming up soon too.) Model M val ❤ :Int val :Int ModelState store + + +
  58. +❤ +❤ + Intent x interface_ModelStore<S>_{ fun process(intent: Intent<S>) fun

    modelState(): Observable<S> }x ModelState store Hi again!
  59. PublishRelay<Intent>.accept(intent) +❤ +❤ + Intent x import com.jakewharton.rxrelay2.PublishRelay ModelState store

    fun process(intent: Intent<S>) = intents.accept(intent)
  60. PublishRelay<Intent>.accept(intent) +❤ +❤ + Intent x import com.jakewharton.rxrelay2.PublishRelay ModelState store

  61. PublishRelay<Intent>.accept(intent) +❤ +❤ + Intent x import com.jakewharton.rxrelay2.PublishRelay ModelState store

  62. PublishRelay<Intent>.accept(intent) +❤ +❤ + Intent x import com.jakewharton.rxrelay2.PublishRelay ModelState store

  63. PublishRelay<Intent>.accept(intent) +❤ +❤ + Intent x scan { , ->

    .reduce( ) } scan { , -> .reduce( ) } ModelState store
  64. PublishRelay<Intent>.accept(intent) +❤ +❤ + Intent x 0❤, 0 0❤, 0

    scan { , -> .reduce( ) } scan { , -> .reduce( ) } ModelState store
  65. PublishRelay<Intent>.accept(intent) +❤ +❤ + Intent x 0❤, 1 0❤, 0

    0❤, 0 scan { , -> .reduce( ) } scan { , -> .reduce( ) } ModelState store
  66. PublishRelay<Intent>.accept(intent) +❤ +❤ + Intent x 0❤, 1 0❤, 0

    0❤, 0 1❤, 1 scan { , -> .reduce( ) } scan { , -> .reduce( ) } ModelState store
  67. PublishRelay<Intent>.accept(intent) +❤ +❤ + Intent x 0❤, 1 0❤, 0

    0❤, 0 1❤, 1 1❤, 2 scan { , -> .reduce( ) } scan { , -> .reduce( ) } ModelState store
  68. None
  69. None
  70. replay(1) connect() PublishRelay<Intent>.accept(intent) +❤ +❤ + Intent x 0❤, 1

    0❤, 0 0❤, 0 1❤, 1 1❤, 2 scan { , -> .reduce( ) } scan { , -> .reduce( ) } ModelState store
  71. open_class_RxModelStore<S>(startingState: S)_:_ModelStore<S>_{ private val intents = PublishRelay.create<Intent<S>>() private val store

    = intents .observeOn(AndroidSchedulers.mainThread()) .scan(startingState)_{ oldState, intent_->_intent.reduce(oldState) } .replay(1) .apply {_connect()_} override_fun_process(intent: Intent<S>) =_intents.accept(intent) override_fun_modelState(): Observable<S> =_store }a ModelStore RxModelStore UpvoteModelStore
  72. interface_ModelStore<S>_{ fun process(intent: Intent<S>) fun modelState(): Observable<S> }x object UpvoteModelStore

    :_RxModelStore<UpvoteModel>(UpvoteModel(0, 0)) open_class_RxModelStore<S>(startingState: S)_:_ModelStore<S>_{ private val intents = PublishRelay.create<Intent<S>>() private val store = intents .observeOn(AndroidSchedulers.mainThread()) .scan(startingState)_{ oldState, intent_->_intent.reduce(oldState) } .replay(1) .apply {_connect()_} override_fun_process(intent: Intent<S>) =_intents.accept(intent) override_fun_modelState(): Observable<S> =_store }a ModelStore RxModelStore UpvoteModelStore Hi again. Btw, I’m not actually abstract. He just goofed up the UML…
  73. object UpvoteModelStore :_RxModelStore<UpvoteModel>(UpvoteModel(0, 0)) UpvoteModelStore

  74. Intent x View Model M val ❤ :Int val :Int

    x x x ModelState store object UpvoteModelStore
  75. Intent x View Model M val ❤ :Int val :Int

    x x x ModelState store object UpvoteModelStore
  76. Intent x View Model M val ❤ :Int val :Int

    x x x ModelState store interface_ModelStore<S>_{ fun process(intent: Intent<S>) fun modelState(): Observable<S> }x fun modelState(): Observable<S> object UpvoteModelStore
  77. View Model M UpvoteModelStore.modelState().subscribe { model -> counterTextView.text = }

    0❤, 1 0❤, 0 1❤, 1 1❤, 2
  78. https://github.com/kanawish/upvote

  79. Live demo

  80. Live demo

  81. https://github.com/kanawish/android-mvi-sample

  82. https://github.com/kanawish/android-mvi-sample

  83. https://github.com/kanawish/android-mvi-sample

  84. https://github.com/kanawish/android-mvi-sample

  85. https://github.com/kanawish/android-mvi-sample

  86. https://github.com/kanawish/android-mvi-sample

  87. https://github.com/kanawish/android-mvi-sample

  88. https://github.com/kanawish/android-mvi-sample

  89. ♻ ☠

  90. * https://github.com/kanawish/android-mvi-sample

  91. https://github.com/kanawish/android-mvi-sample

  92. https://play.google.com/store/apps/details?id=com.shopify.logomaker.hatchful

  93. Photo source info here Thank you! https://github.com/kanawish/android-mvi-sample https://github.com/kanawish/upvote