Why MVI? —The State Problem

This is the second article in Why MVI series. You might be interested to read about the motivation, for that you can have a look at the introductory article here.

To understand what is The State Problem, let’s first have a look at what is State and how we can model it for a particular screen.

State

That’s my favourite word so much so that I even created a graphic about it :

Any variable which is used to depict what a view component is currently doing is it’s state. View is currently refreshing. Login Button enabled. Current Page Number. List of Items. Error Message. Is User Logged In. etc. Altogether, these constitute state of any screen.

In MVI, State is the single source of truth. Instead of View, Presenter and Data Layer having their own states we have just one State object which represents the state of the whole screen.

After the dramatic introduction to State with a graphic, let’s try to model a State object for a screen.

ItemsFragment

.

ItemsFragment

We have a screen called ItemsFragment as shown in the diagram. We will try to model a State object for this screen. For simplicity we are not considering the TabLayout or BottomNavigationView and instead going to just focus on the highlighted part.

.

Loading
EmptyStateVisible

.

.

.

Loading

The progress indicator which indicates that the items are currently loading can be represented as a Boolean: loading. Currently the List of items is empty.

.

.

.

EmptyStateVisible

After getting the results from our Repository, we could get an empty result which will show an empty state on the screen. It can be represented as a Boolean : emptyStateVisible.

Refreshing

.

.

.

Refreshing

User can also pull to refresh the screen which can be represented as a Boolean : refreshing. Do note that loading and refreshing are two separate states as they represent state of different view elements.

No Internet

.

.

.

No Internet

Next we have an error state of the view which can be represented as a String : errorMsg on the screen. It is currently set to “No Internet”.

Items List

.

.

.

.

Items List

In case our view shows some items, our list is going to be non-empty and state will contain List<Item>.

.

.

State Problem

Let’s now have a glimpse of a simple state problem in an app below.

State Problem: Pull to Refresh keeps showing forever

We are loading some items and we thus see a loading indicator. Then, we get an empty result from server and we show an empty state to the user which says “No Items”. User then hits Pull to Refresh and even though we get an empty response from server again, the pull to refresh indicator keeps showing forever.

This is a simple example of a state problem from one of my own apps. These kind of issues keep creeping into our apps from nowhere. I have seen many good apps with issues such as Error State and Loading indicators shown at the same time.

Misusing MVP

Let’s figure out how we caused the above issue by misusing our favourite pattern.

Representing MVP

Diagram shows various elements in MVP + User. Time is represented on the y-axis and increases from top to bottom.

.

.

.

.

User first opens the app (start()) and View starts loading data. View calls showLoading() on itself which shows a ProgressBar on the screen. View changes it’s own state without Presenter knowing anything about it. This is one of the ways in which we can misuse MVP.

View then calls loadItems() on the Presenter. Presenter gets data from the Repository and we get an empty result. Presenter calls showEmptyState() and hideLoading() on View.

Presenter tries to drive the state of the view and some of the state is kept in Presenter while some is kept in the View.

User hits pull to refresh on the screen (refresh()). View calls setRefreshing() on itself. Again View changes it’s own state without Presenter knowing anything about it.

View then calls refresh() on the Presenter. Presenter gets data from the Repository and we get an empty result. Presenter calls showEmptyState() and hideLoading() on view.

Presenter knows nothing about the refreshing state of the view.

.

.

Presenter only knows about the MVP contract which exposes a method called hideLoading() and that’s what Presenter calls.

There’s no loader to hide and thus the Pull to Refresh indicator keeps on showing forever and we run into a State Problem.

What went wrong?

  • Presenter, View and even Business Logic : everything has it’s own State.
  • Presenter coordinates View State.
  • Presenter has multiple outputs and as the complexity of screen increases, it becomes hard to manage these output sites from Presenter.

Solution

If you remember, we created a single state object depicting the above problem. If there’s a single source of truth between various layers and only a single place where it can be modified. If there is only a defined set of actions that can be performed and for each action there’s a defined way to change the state of our system. We can then solve this problem. This is what MVI suggests.

In the next part of the series, we will look at how we can extend our existing patterns and apply the guidelines from the MVI world to solve the above problem without having to start from scratch. Sample repository can be found here.

If you are interested, I also gave a talk at Droidcon Boston 2018 regarding this and you can watch it here.

Okay! Now, Clap Clap 👏.