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.