Swipe-to-Refresh with LiveData and MVVM

This post assumes prior knowledge of Android’s Architecture Components.


Problem

How to implement Swipe-to-Refresh with LiveData + MVVM.

TL;DR


Explanation

Initial Implementation (Don’t Do This!)

LiveData is a little tricky when you’re first learning how to use it and it’s very easy to misuse it.

Initially, I thought I could simply observe the exposed LiveData object provided by the ViewModel via

usersListViewModel.getUsers()

and calling that method whenever the user Swiped-to-Refresh on the User List. My assumption was that the observed LiveData object would fire every time data was posted to it and everything would be great!

This actually works, but it comes with a nasty side effect.

Every time we call

userListViewModel.getUsers()

we register to a new LiveData instance without unregistering from the previous one. In order to get this to work properly, we would need to keep a global reference to an Observable and make sure to register and unregister manually every time the user Swiped-to-Refresh. Not fun!

Revised Implementation (Do This!)

Instead of naively calling userListViewModel.getUsers() every time we want fresh User data, we’ll need to perform a Transformation on our original LiveData object. Specifically, we’ll need to make use of Transformations.switchMap(), which takes in two parameters: a source LiveData object, and a switchMapFunction that will ultimately return a “most recent” LiveData object as a result of the switchMapFunction.

What…?

To better explain how this works, let walk through an example. Let’s say we had the following:

private val searchTermLiveData = MutableLiveData<String>()
private val usersLiveData: LiveData<List<Users>> = Transformations.switchMap(searchTermLiveData) { searchTerm -> 
    userRepository.getUsersBySearchTerm(searchTerm) 
}

fun updateSearchTerm(searchTerm: String) {
    searchTermLiveData.value = searchTerm
}

Every time a value gets posted to searchTermLiveData, the switchMapFunction for usersLiveData fires and returns an updated LiveData object and the data changes are dispatched to the observer on the main thread. In addition, the switchMapFunction handles the deregistering/registering of the previous and new LiveData objects, respectively.

So, the revised implementation of Swipe-to-Refresh looks like this:

Whenever we want to refresh our list of users (in this case, whenever the user swipes downward on the list), we post a boolean value to our reloadTrigger MutableLiveData object.

This fires the switchMapFunction (userRepository.getUsers()) for our users LiveData object and our list is updated.

The downside of this is that we’re posting a “dummy” boolean value to reloadTrigger, but this speaks more to LiveData’s inability to scale well.

Lastly, this is what your View (Activity or Fragment) would more-or-less look like:


Takeaways (Pros and Cons)

LiveData is great because it’s lifecycle aware which ensures that we won’t have any leaks because Activities and Fragments unsubscribe to its source whenever they’re destroyed.

Another perk is that LiveData always dispatches its events (data changes) on the Main Thread. This simplifies things because we don’t have to about accidentally updating the UI from a background thread and throwing an Exception.

However, after implementing Swipe-to-Refresh with Architecture Components and LiveData, I’ve learned LiveData doesn’t scale very well. I was very surprised I had to wire up my ViewModel this way to listen to events and update the UI.

You can check out the full implementation here.