From MVI to MVI v20 — Model-View-Intent Journey Part II
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.
Let’s recall MainPresenter.kt — the class where all interesting stuff happened in our last post:
This is how its bind method was called inside MainActivity.kt:
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 Subject:
And return new emitter in one the view’s methods:
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 MainActivity :
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.
Check out available software development job opportunities at Untitled Kingdom: