How I Learned to Stop Worrying and Love the ViewModel

Evan Teters
QuarkWorks, Inc.
Published in
6 min readApr 16, 2021
Design by Hannah Pratte

Introduction

In 2020, Google made it clear that Android Jetpack and Kotlin are the future of Android development (and a lot of other things). Google and the Android Developers team have been shouting from the mountaintops how their new powerful language features and libraries can cut down on dev time, reduce crashes, and much more. Among these powerful language features and opinionated libraries is the ViewModel: (arguably) the heart and soul of the MVVM pattern they are championing.

The ViewModel and observer pattern is not new — and Android developers have been talking about them for years. But if you’re like me: new to Android development or just now catching the attention of Google’s Jetpack Crusade, then this article is for you. Our project started three years ago is written in Kotlin, and until August was entirely MVC. At the ripe age of three, we found that our View layer was quite unwieldy. While the logic in the Model was separate, the logic determining the interaction of data with the View was complex and hard to manage.

Jetpack caught our eye with ViewModels, and after some discussion, we dove right in. We hoped ViewModels would make our project easier to work in, and so far, that has proved to be right. With this article, I want to pass along a few things we picked up on our journey that we found useful.

The first question we had was:

How do we get started, and how do pass an argument?

1. ViewModel Factories when arguments are needed

ViewModel Factories can be confusing at first glance. They’re hidden from you unless you need them:

This doesn’t make it immediately clear how to initialize ViewModels with arguments! That is where getDefaultViewModelProviderFactory comes in. The by viewModels syntax is utilizing a lazy method that initializes the default Factory for your ViewModel and gives you the instance of that ViewModel for your view when you need it (that instance is created and destroyed when its Fragment/Activity is created or destroyed). Overriding getDefaultViewModelProviderFactory lets us change that default factory to a custom one.

If you’re still primarily using activities and intents, you can get data from intents to pass to your viewModel here:

You can also get data from your navigation arguments:

Keep in mind, we’re overriding the default, which is otherwise generating a ViewModelFactory for you. To use a custom one, you’ll need a new class file. Here is the one we use above:

This can be boilerplate for most custom factories you need.

Next, we found we needed guidelines to help keep our ViewModels clean and easy to work in.

2. Props for clarity and separation

At QuarkWorks, we’ve come to frequently use React’s concept of Props. The model for the view (also sometimes called the ViewModel, an overloaded definition we won’t use here) exists purely, so we can define all the data needed for the View to display itself.

We define Props in each View, such that we have ExampleView.Props for every view. This was a welcome departure from complicated configure functions requiring various inputs.

There are a few things going on here, marked with A-C:

A) The Props themselves, a data class, are holding the information needed for the view. If needed, they can hold props for sub-views.

B) The companion object provides us a “factory” for clearly defining what is needed to populate the view.

C) The instance of Props on the View is the same in all cases. When it updates, we call a function to assign the three fields to their corresponding UI.

Why is this useful for ViewModels?

Android MVVM relies on a uni-directional flow of data via LiveData. With Props, LiveData is simple to set up:

What if we don’t want to keep a private mutable store of all the view props in the viewModel? That is what we use State for.

3. State to manage your private, mutable backing data

We also like to create a State object in most of our ViewModels. This helps keep track of all the underlying data that can inform the view. This is especially useful if your view is driven by multiple API calls.

This is also inspired by React! It turns out their philosophy works well with MVVM anywhere, as unidirectional data flow is central to the architecture.

With this one instance of private MutableLiveData, you can then base the rest of your public LiveData off of it. We can modify the above _exampleViewProps and exampleViewProps as a single exampleViewProps:

This leverages one of the powerful LiveData transformations, map. It creates an intermediate liveData that will update whenever the state updates. In this way, your viewModel will only need to worry about updating the one private MutableLiveData.

But how should you update your state? Here is how we do it, with a little setup:

This extension lets us safely update MutableLiveData. Then…

Look at how easy it is to safely update the state and propagate your API results to all of your UI!

It’s outside the scope of this post, but you could also build a diff-friendly version of this that propagates changes to the UI only when certain aspects of the state change. In its current state above, updating relatedData will also send a signal to the exampleViewProps.

There is one last question that came to us while working with viewModel:

How do we allow complex UI to respond to service layer errors?

4. Result to keep the view aware of errors in a flexible way

Most of our ViewModel LiveData Props are also wrapped in Result. Here’s why: Let’s say you wanted to pull from two different APIs to update two different parts of the screen. One fails, and one doesn’t, but you’d really like to preserve as much of the experience as you can. Being clever with Result wrapper classes can help you propagate server-side errors to the View so you can respond on a case-by-case basis. This beats having another LiveData “errorState” to keep track of for each section of the UI that could fail. More streams of information are more for you and other developers on the project to keep track of.

There are many implementations of Result. A bit of searching will reveal some good open-source options like kotlin-result. Kotlin also contains its own Result class that should work well for most cases. In the below cases, I’m using a custom Result monad that was built for one of our projects, but most Result implementations should behave similarly, if not the exact same.

Using result will slightly modify all of the code above so that we have the following:

It’s worth pausing to discuss how Result.map is different from Transformations.Map. Result.map is marked with A). Here, it will perform the type transformation in the block on success, otherwise, return the original APIException.

Finally, in the view, we have a different way of observing the liveData props wrapped in result:

5. Putting it all together

I put it all in one place so we could admire it all together:

Exciting, isn’t it?!

Disclaimer: ViewModels are in no way similar to a bomb

With some work establishing these patterns, we’ve learned to love the ViewModel, and I hope you have too!

As ever, QuarkWorks is available to help with any software application project — web, mobile, and more! If you are interested in our services you can check out our website. We would love to answer any questions you have! Just reach out to us on our Twitter, Facebook, LinkedIn, or Instagram.

--

--