android architecture: Part 7: ViewModel with LiveData and RxJava

By | November 6, 2018

Scope

In part 6, I rewrote the movie search app in MVVM using LiveData  and ViewModel  architectural components from Jetpack. The app looks good in portrait mode, but there is an issue when screen rotates which is due to what I call an incorrect use of LiveData in part 6. This was deliberately made to draw your attention to a delicate point on where to use LiveData or RxJava.

In this part, after explaining the error, I try to shed light on how I believe LiveData and ViewModel shall be used together in android apps. The branch mvvm-architecture_component-liveData&viewmodel-revised on the series repo on Github reflects the fix to this error.

By the end of this part, I hope you will gain a clear understanding of where to use each.

 

Let’s face the issue

The issue happens when user clicks on an item on the list, hits the back button on the detail screen and returns to the list. Now when rotating the screen, the immediate screen shown is the same detail screen, this time without clicking on the list.

 

 

How does this happen?

Well, let’s see the code:

This method from MainViewModel  is called when there is a click on a list item:

where  val itemObservable = MutableLiveData<MainModel.ResultEntity>()  is a  LiveData .

So click on a list item causes LiveData  to post a new value which is then observed in the MainActivity :

which navigates to the DetailActivity .

Here, we did not click on any list item after screen rotation. We also know that Activity  is destroyed and recreated on such configuration change. Hence there shouldn’t be any track of our previous clicks since we did not manually stored them before rotation. So why does this happen?

The answer lies in the mutual action of ViewModel  and LiveData in Jetpack architecture components. In fact, by assigning a  MainViewModel  extending  ViewModel  architecture component to our MainActivity :

we tell the operating system that we do not want it to create a new instance of  MainViewModel  every time  MainActivity  is recreated. So the system uses our existing instance of MainViewModel  before and after rotation, making it a singleton.

The other thing that the OS does is that it reposts the last value posted to each   LiveData  inside the ViewModel  inheriting from Jetpack ViewModel before screen rotation. This causes all three LiveData  s inside our MainViewModel  to be posted their last values before rotation automatically from the OS:

The first LiveData  ( resultListObservable ) makes its observer in MainActivity  to show the same list of search results before screen rotation to the user. Alternatively, if there was an error before rotation, the observer to the second LiveData  ( resultListErrorObservable ) shows the same error page. Finally, the third LiveData ( itemObservable ) receives the same item clicked before rotation by the system causing its observer to show DetailActivity .

 

Is this a negative thing with LiveData? Should we avoid LiveData?

Absolutely not. There is nothing wrong with LiveData , the problem is with our misusing it here.

In fact, LiveData  together with the ViewModel  from Jetpack architecture components were introduced to provide coherent and consistent UI to the user before and after configuration change by storing view-related data in LiveDatas inside the ViewModel  and emitting them again on Activity  recreation. This causes the different view components of the app to receive the exact same data before and after configuration change, rendering a seamless user experience.

On the other hand, LiveData  handles lifecycle management and leak issues itself so we don’t have to care about disposing our observers in, for example, onDestroy()  method of the Activity .

If we wanted to use RxJava  instead of LiveData  for the same functionality above, we had to use BehaviorSubject . In other words, LiveData  acts like a simplified version of  BehaviorSubject  in RxJava  which emits the most recent item before subscribing to it and all subsequent items to the observer.

So any attempt at changing the default implementation of LiveData  , like the one I saw on a forum on extending it to change the default behaviour to prevent it from posting values on subscription, would not probably be inline with why LiveData  was created in the first place.

In my opinion, LiveData  should only be used for populating the view elements with the same data we want to show before and after configuration change. Anything beyond, such as trying to handle streams, making it work as RxJava , etc, is misusing LiveData.

 

Now how to fix this example?

There are two ways:

1- [Simple way] Change the structure of the the code in a way that there is no need to post a value to LiveData  in the ViewModel  to handle button click:

For this, one line in onBindViewHolder of the listAdapter shall be changed from

to

Then inside MainActivity , the following must be changed from

to

thereby bypassing the MainViewModel  method call.

2- [More complex way] Use PublishSubject  instead of LiveData  in MainViewModel , so that it does not emit the items before subscriptions to the observer:

Then in MainActivity :

I have used the second way for fixing this issue, as found in ‘branch mvvm-architecture_component-liveData&viewmodel-revised’.

Here is the result after applying the fix:

 

 

 

What is the takeaway?

Even though LiveData  uses the observer pattern for notifying the observers, it should not be compared with RxJava. In edge case, it can be considered a simplified version of BehaviorSubject  in RxJava with internal lifecycle management. Its function is to populate the view elements with their respective data before and after configuration change as well as to stop listening to this data when Activity no longer exists (such as onDestroy ). In doing so, ViewModel and   LiveData from Jetpack architecture components work like pancake and cream (you may read peanut butter and jelly).

So it is great to use LiveData  together with ViewModel  in modern app development, but be ware of misusing LiveData . RxJava is always there for you.

Please help by spreading the word:

Leave a Reply

Your email address will not be published. Required fields are marked *