Keeping .observe() out of the ViewModel

| Colin Dodd

If you've been using Android Architecture components you've probably got a nice separation of concerns. A View, doing everything UI; a Model, keeping control of your data sources; and a ViewModel, shuffling data between the two. LiveData makes it trivial to communicate between these layers, but how do you consume a LiveData in the ViewModel when you don't have a LifecycleOwner?

One of the big advantages of LiveData is that they are lifecycle aware. When you observe a LiveData you send in a LifecycleOwner and it ensures that the observers are only informed of changes if they are in an active state. It's great, you can observe and not have to worry about memory leaks, crashes, or stale data.

It's trivial when observing a ViewModel's LiveData in the View; but what about the Model to the ViewModel? In my previous article I spoke about how you can use NetworkBoundResource in the Model to return cached database data whilst fetching fresh data from the network. NetworkBoundResource exposes its data through a LiveData which means the ViewModel needs to be able to observe it; but a ViewModel is not a LifecycleOwner nor does it have a reference to a LifecycleOwner. So how can a ViewModel observe these changes?

The wrong solution

There is a method called observeForever - it will allow you to observe without passing a LifecycleOwner but it means the LiveData is no longer lifecycle aware. To stop observing you'll need to remember to call removeObserver.

I've made use of observerForever in tests, but never in production code. I would be interested in hearing under what circumstances observerForever should be used. When it comes to observing in the ViewModel, there is a better solution.

The right solution

The Transformations class is what you need to keep observe out of the ViewModel. With its map and switchMap methods you can consume LiveData from the Model, and transform that LiveData into something which the View can observe.

Say you are developing an email app and you want to show the total number of unread messages. The Model only allows you to get a list of unread emails. The ViewModel however can transform the data from the Model into the unread counter that the View needs.

fun getNumberOfUnreadMessages(): LiveData<Integer> {
   return Transformations.map(model.getUnreadMessages(), { it.size })
}

model.getUnreadMessages is returning a LiveData<List<Email>> which is being mapped to the size of the list. This method returns a LiveData<Integer> which can be consumed by the View. No need to observe in the ViewModel.


switchMap is useful if you need to send a parameter from the View to the ViewModel. Imagine the situation where the user can search through their email with a search query.

fun searchEmail(query: String): LiveData<List<Email>> {
    return model.searchEmail(query)
}

The problem with the above code is that every time the View calls searchEmail a different LiveData will be returned, so the View will need to keep detaching itself and attaching itself to the LiveData returned from this function. By using switchMap we can ensure that the same LiveData is returned and the View doesn't need to do anything special at all.

private val userInputtedQuery = MutableLiveData<String>()

fun searchEmail(query: String) {
    userInputtedQuery.value = query
}

val searchResult = Transformations.switchMap(userInputtedQuery, { model.searchEmail(it) })

By using switchMap we can observe changes to one LiveData, and trigger the calling of another function. The LiveData is transformed and returned to the View where it can be observed; avoiding having to call observe in the ViewModel.

Conclusion

Make use of the Transformations class to keep observe out of your ViewModel. This keeps your LiveData lifecycle aware and gives you all the benefits that entails.