Android Architecture Components: LiveData
Motivation for using Architecture Components: Architecture Components were developed with the aim of developing Android…medium.com
This way it provides the following:
- It always update the active observers, those their LifeCycle is at least STARTED or RESUMED. If the observer’s associated LifeCycle is DESTROYED, the observer will be removed and will no longer receive updates from the LiveData object, thus avoiding memory leaks.
- Even if the Activity/Fragment is stopped and the observers become inactive, LiveData makes sure that the observers receive the latest data when they become active again.
- LiveData can survive configuration changes (like device screen orientation change) and provide the observers with the latest data, this is achieved through encapsulating an instance of LiveData within a ViewModel class.
Now let’s see how we can use LiveData, we’ll create a very simple app that observes a LiveData instances, for now we will not use a ViewModel so our LiveData objects will not survive configuration changes.
We’ll create a simple class called DemoDataSource it has a private MutableLiveData instance that’s exposed to clients through a another LiveData object.
This class will initially update the observers with a list of integers:
And we’ll have a method that appends a new item to the integers list:
So in the end it looks like this:
We notice that we update the value of our LiveData objects using the postValue(value:T) method, this can also be done using the setValue(value:T) method, the main difference is that setValue must be called from the Main thread, while postValue can be used from a Worker thread. For the sake of the simple demo we’ll use postValue for now.
Now to our Activity, we can observe the LiveData like this:
The LiveData object has the observe method which takes a LifeCycleOwner and an Observer as arguments.
We usually start observing LiveData in the Activity’s onCreate() to make sure that when the Activity is in an active state (LifeCycle.STATE.STARTED) it would receive the latest data from the LiveData object.
Now we’ll add a button to the Activity’s layout and on the button click we will trigger adding a new item to our LiveData:
When we run the app, and click on the button, we’ll find that our UI will be updated with the new value of our LiveData, that’s because our Observer.onChanged is always invoked whenever the LiveData object is updated via setValue() or postValue().
Another scenario: we will add a new item to our LiveData whenever the activity stops:
We can see the effect of that by running the app, pressing the home button (the activity becomes inactive) then opening the app again (becomes active), we’ll see that the observer is notified with the new LiveData value and the UI is updated.
We can use the Transformations class to make changes to the values stored in LiveData before dispatching them to observers.
We can for example change the type of the data emitted from our LiveData from a List of Integers to a List of Strings using Transformations.map() like that:
Another transformation method is switchMap(), which is similar to Rx FlatMap operator, it takes a LiveData object that emits a certain type, applies a transformation function on it and returns a new LiveData Object.
The newly returned LiveData will always dispatch updates to the observer whenever the original one is updated.
So for example we can use switch map to get a new LiveData object that emits the sum of all the items in our original dataSource.numberLiveData:
We see that we have a new LiveData object: sumLiveData which emits the sum of all the items emitted from dataSource.numberLiveData.
Whenever dataSource.numberLiveData gets updated, sumLiveData will be updated too and its observers will be notified.
Merging multiple LiveData objects
Using MediatorLiveData We can also combine multiple LiveData objects into a single LiveData and receive updates whenever any of the source data sources is updated:
Here we have two LiveData sources: source1 and source2, and we have the Mediator Live Data merger which combines both. Observers of merger will be updated whenever any of source1 or source2 are updated.
Now we write some unit tests, first we’ll check that our LiveData object inside DemoDataSource has the correct values after calls to postValue():
Here we are verifying that the LiveData object is initialized with the correct values, after running the test we get the following exception:
java.lang.RuntimeException: Method getMainLooper in android.os.Looper not mocked.
That’s because dataSource.loadNumbers() calls _numbersLiveDate.postValue(numbers) and since postValue() posts the result (asynchronously) to the main thread, we will add the InstantTaskExecutorRule to make sure that LiveData values are posted synchronously:
To use the InstantTaskExecutorRule, we need to add the following dependency:
Now we’ll verify that an observer of the above LiveData will be notified with LiveData values when they get updated:
Here we notice that in the setup() method we moved the life cycle to the STARTED state:
That’s because observers do not get updated with LiveData values unless the life cycle is in active state (STARTED or RESUMED).
And that was if for Android LiveData, source code for the demo app available on GitHub.