From MVP to MVI blog post marked the beginning of our journey, but instead of relentlessly pushing forward let’s sit for a while, take a rest and try to improve our approach. We highly encourage you to read about our previous adventure as this article is basically a list of three crucial improvements to implementation explained there.
Issue 1: Going for partial states.
Sign up for Untitled Kingdom newsletter
MainPresenter.kt — the class where all interesting stuff happened in our last post:
This is how its
bind method was called inside
One of the things bothering us was that each time user decides to switch the screen off and then turn it on again (onStart-onStop loop) the cached state (from presenter’s
BehaviorSubject ) will be partial, i. e. it will have to go through the
reduce method and only after these computations it can be pushed to main view. Seems suboptimal, right?
The right thing to do is obvious — caching
MainViewState instead of
PartialMainViewState . Below we present the updated implementation. Notice usage of
createDefault() method on
BehaviorSubject . This subject is now a proxy for full, renderable states returned from
reduce instead of a proxy for partial states returned from
mergedIntentsObservable after flatmapping intents (user actions).
Issue 2: Large reducers.
You may not have noticed, because of the simplicity of the 3-state example app, but
reduce method inside Presenters in MVI (or inside ViewModels, whatever you want to call them) can get fat…really fat:
We can’t get rid of all those lines of code, but we can position them cleverly and adhere to single responsibility principle. Recall, that partial states are encapsulated inside sealed class:
Besides state, we can also encapsulate behavior by creating a new
reducecontract with which concrete partial states should comply:
And this is how
reduce method inside
MainPresenter looks now:
Much simpler, isn’t it?
Issue 3: Those awful subscriptions.
In real world scenarios you will use
PublishSubject a lot to emit events from the view. For demonstration purposes, let’s imagine for a minute a world without a great Jake’s library called RxBinding. How would you go about emitting a button click in a reactive way? Well, it’s not that hard. All we need to do is declare
And return new emitter in one the view’s methods:
Sign up for Untitled Kingdom newsletter
Let’s take a closer look at rendered states, when a
Subject is used to emit intents, by adding a simple log inside
render method of
These are the results of some experimentation:
Initial app start produces single state — the default one (empty list):
Now user clicks the “show me rockets” button and two new states are added: one indicating progress and the other with fetched list:
Since full view state with fetched list is cached in
BehaviorSubject, then after going through onStart-onStop loop e. g. by putting app to the background, we receive that state again:
And one last time:
Now comes the tricky part. What do you think will happen if user clicks “show me rockets” button after going through 3 onStart-onStop rebindings? We expect two states again: one indicating progress and the other with fetched list. However 8 new states are added:
Each time we go through onStart-onStop loop (rebinding) we subscribeto the same
PublishSubject we created once in the field of
MainActivity!So after putting app to the background 3 times we add 3 additional subscriptions that will simultaneously react to the item emitted by single stream connected to button click. The solution is to define
Subject emitters during every binding like so:
This way only one subscription will be related to one emitter at any given time.
Hope you enjoyed our improved version of Model-View-Intent. Feel free to send us message or comment if you’re in trouble implementing a similar reactive architecture yourself. Cheers!
- From MVP to MVI — Model-View-Intent Journey Part I
- How I lost few hours because of using wrong type of parenthesis
- Objective C vs Swift messages dispatch
- Why we loved working on Elvie