Building Decoupled Views with MVVM and RxJava: Things to keep in mind
A few things to consider when trying to write decoupled, reactive, testable, and lean Views using MVVM + RxJava.
Let them be Views
In the grand scheme of things, your view’s job is typically very succinct. Views are limited to displaying, hiding and updating interface elements, as well as reporting user interaction. Any business logic that you decide to write in your View, ranging from complex control flow logic to a simple string concatenation becomes a wasted opportunity to offload such work off to a background thread. When devoid of such, your Views become:
- Much smaller, since your View’s responsibility is simply binding data streams.
- Easier to read, since your Views aren’t hiding obscure business logic anymore.
- Easier to test, since you now only have to mock your incoming data streams, and no longer worry about testing any business logic in your View.
- Less bug prone, thanks to a combination of the above three.
Keep the Model away
Consider this generally harmless example where we are wanting to display user information on a simple page with a profile picture, a name and a bio:
Taking a look at
UserFragment , it’s clear that the Model bleeds into the View. Our View now knows about the
User class, what fields to read from it, and becomes strongly coupled to it. Refactoring our
User model now has direct consequences onto the View. To avoid this scenario, a good solution is to create intermediary classes that directly model the data and the state that the View will be in, and let our ViewModels provide them:
Using Kotlin data classes for those classes come with a lot of advantages, such as immutability, default values, built in
Using those intermediary classes has the benefit of allowing you to include extra information regarding your View’s desired state. In our example, we may need to choose a placeholder image when loading the User profile picture. This is a perfect place to do so, rather than hardcoding it’s value somewhere in the Model or the View.
This pattern is also useful when developing new User-Facing features before the supporting code is fully fleshed out. Fields can be added and modified to this class so you can test View state even before implementing your model changes.
Tracking View States when debugging also becomes easier if using Kotlin data classes, thanks to its built in
toStringmethod. Simply log the received class instance and you will have a clear history of View State transitions.
UIs typically consists of a combinations of elements that can either stay static, change as a unit, or change independently. Understanding your UI’s structure and behaviour can help you avoid cases where you’re unnecessarily updating your user interface, which can be especially costly when dealing with lists.
Let’s take a look at Pocket Cast’s podcast details page, and try to isolate bits of data that can change as a unit, or independently.
The top section consists in a collapsible title with a background image, on top of a dynamically changing background colour based on the current content. Modeling this would be:
Next, the center section is a list of podcasts with a name, a publication date, a clickable colored icon with text on the side that reflects download progress and playback status with remaining time. The text, the icons and and the progress value change independently, so we’d model them separately. In this situation, each
ViewHolder element of that list will likely have their own
ViewModel that would be delivering the appropriate updates.
The list can change has a whole, so let’s model it as well:
Finally, the bottom handle showing the currently playing content also has three independent blocks of data. The content’s metadata (title, image, subtext, background colour), the player’s icon and color, and the player’s time elapsed:
To follow up, let’s take a look at what the handle’s ViewModel may look like:
No doubt that this is only a subset of the display logic in the Pocket Cast application which surely includes a lot more states (and probably better naming), but this could be a start. Focusing around building our ViewModels to provide those blocks of information is a good way to keep our Views relatively light and resistant to change, yet responsive.
Edit: Modeling View Interactions
I received an extra question about modeling View interactions (clicks, pulls to refreshes, text edits, etc..) and how it looks like in a MVVM setup. What’s great about this, is that you can think and model your data streams based what type of information on the interaction carries.
Consider this mini Example: We have a simple page that shows a post, and this page also has a refresh button that as expected, reloads the post. Take a look at this gist (uses Jake Wharton’s RxBinding):
Here, we model the button click interaction in
PostViewModel by passing a data stream representing clicks as an argument to the
fun refreshClicks(requests: Observable<out Any>): Observable<Any>
What’s great about this setup is that it affords the opportunity to pass in any sort of stream as the
request parameter, while keeping the ViewModel decoupled from the View elements. We could substitute it with a stream that represents Pull to Refresh interactions, long clicks, or event a timer Observable that would refresh the post on an interval.
Those are some of the things I try to keep in mind while designing View code for MVVM. If you think that this post was completely off the mark and that the approach contains some issues, or that there are some things that were missed, please comment below!