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.

Etienne Caron

April 09, 2019
Tweet

More Decks by Etienne Caron

Other Decks in Technology

Transcript

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

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

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

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

    :_MainViewEvent() }_ MainViewEvent ThumbsUpClick LoveItClick + +❤ ❤ ❤
  5. View ❤ ❤ class MainActivity {_ override fun viewEvents(): Observable<MainViewEvent>_{

    return Observable.merge( heartButton.clicks().map {_LoveItClick }, thumbButton.clicks().map {_ThumbsUpClick_} )_ }_ }_
  6. /** * 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 ❤ ❤
  7. 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
  8. 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 ❓ ❓
  9. 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
  10. 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.)
  11. ModelState store Intent x View Model M data class UpvoteModel(val

    hearts:Int, val thumbs:Int) val ❤ :Int val :Int x x
  12. 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
  13. 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
  14. 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
  15. 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 ❓
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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 + + +
  21. 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 + + +
  22. +❤ +❤ + Intent x interface_ModelStore<S>_{ fun process(intent: Intent<S>) fun

    modelState(): Observable<S> }x ModelState store Hi again!
  23. PublishRelay<Intent>.accept(intent) +❤ +❤ + Intent x scan { , ->

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

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

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

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

    0❤, 0 1❤, 1 1❤, 2 scan { , -> .reduce( ) } scan { , -> .reduce( ) } ModelState store
  28. 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
  29. 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
  30. 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…
  31. Intent x View Model M val ❤ :Int val :Int

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

    x x x ModelState store object UpvoteModelStore
  33. 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