ViewState and Interactions — an easy contract between view and ViewModel

What I liked in MVP architecture approach was the interface, the contract between a Presenter and a View (yes, I was the one who created an interface for each Presenter :). In a glace I knew, what user could do on that screen and I could imagine how the screen looks like by going through methods Presenter calls on the View. Everything was clear, everything in one place.
When moving to MVVM I kind of a lost it. Don’t know why to be honest but with this new way of handling presentation layer communication I just did it differently. I decided to came back to my lovely contract. Is it possible with ViewModel
? Of course it is.
Let’s take this one as an example:
Simple Fragment
example. Imagine we’ve created screen with settings that allows us to change our name, enable/disable notifications and go to other, more specific settings screens. Now let’s take a look at ViewModel
.
This implementation of ViewModel
is also nothing complicated. We can see there user interactions methods and couple of LiveData
s for views changes. There is nothing wrong about this ViewModel
. It looks pretty good, right? I’m just missing my contract… or at least some place that I could go and see what is happening in this screen. What could be changed, as simple as possible, to make me happy?
What do you say about these changes?
First, I’ve created ViewState
class which corresponds to everything that could be shown on the screen, each state of each view. Now just this one class will be used to render the screen data.
Second, Interaction
class was introduced. It’s a collection of interactions that user can perform on the screen.
Of course I could wrap these two classes into an interface to make it more formal and mandatory to implement by both the Fragment
and ViewModel
, but to not complicate this example with additional inheritance, let’s just leave it as it is, it’s simple now.
Ok, so how our SettingsFragment
looks like now?
Nothing really changed, right? It looks almost the same, but now from its perspective performing actions are more similar to calling commands. Rendering view’s data also is very similar to what we had before, with the small difference — now there is just one observer and we render the state as a whole.
What about SettingsViewModel
?
Couple of things have changed here:
currentViewState
— it’s aViewState
cache, when submitting newViewState
we can just change some of its properties, take a look atnotificationsSwitchChanged()
method;onInteraction()
— method which handles all of the interactions;- public methods becomes private.
The rest of the code is pretty much the same.
Pros and cons
What I like about this solution:
- Description of what is happening in that screen can be found in one small file.
- One view state which defines everything that could be displayed, every state of every view. In most cases this class is small and easy to change.
- When some new view is added to our
Fragment
, all we need to do is to add another property to theViewState
class and handle it inViewModel
. No other property logic is changed, thanks to copying from thecurrentViewState
cache. ViewState
could be easily refactored in a way that more complicated views could have its own state structures, so theViewState
instead of having just primitives, could have different states objects for different views. For exampledata class ViewState(val toolbar: ToolbarViewState, val settings: List<SettingItem>, …)
.- One
seald class
which lists all of the interactions. - One
when
function which handles them in a nice command way. - A lot of other architecture changes that we can make now, like mapping interactions to use cases or logging analytics events based on them.
Why it causes me a headache:
- Sometimes a lot is going on, screen can have many views and complicated logic. The
ViewState
could become huge, adding property by property to it could be hard to handle or refactor in the future, because those properties could be strongly coupled. Simply put, it’s easy to end up with a mess. - Paradoxically, with complicated screens we might want to split this one
LiveData
with oneViewState
to a couple of smaller ones, but I think it’s not the problem of this particular idea, but in general the problem of modularization and extraction the logic to smaller, independent pieces.
If you want to take a look of how we could now extract interactions logic out of the ViewModel, I encourage you to go to this example:
Thats it! Couple of small changes and the code looks a bit more readable. The communication (the contract) between the Fragment
and ViewModel
is now written down in one place and thanks to that both of the classes are now better structured.
Thanks for reading! Hope you enjoyed!