What is LiveData?

LiveData is part of Android Architecture Components released by Google. It is an Observable(it follows observer pattern) class that holds data of type that you specify. If you have worked with RxJava, it is similar to an Observable. In case of Observables you have to dispose them off manually but LiveData is lifecycle aware and will do all the cleanup on its own(If you are new to Architecture Components then read about Lifecycle before reading this article any further).

LiveData takes in an observer and notifies it about data changes only when it is in STARTED or RESUMED state. (Again, if you don’t understand what these states mean and please read the article on Lifecycle). After reading this article you must go through the official docs on LiveData.

Adding LiveData to your project

In your app level gradle file, add the following dependency.

implementation "android.arch.lifecycle:extensions:1.1.0" 1 implementation "android.arch.lifecycle:extensions:1.1.0"

In your project level gradle file, make sure you have google() in the repositories section.

allprojects { repositories { jcenter() google() } } 1 2 3 4 5 6 allprojects { repositories { jcenter ( ) google ( ) } }

Creating LiveData

In my experience LiveData are best suited with ViewModel(another architecture component) which we will learn about in next tutorial.

LiveData holds a value which you provide it and you can get the value by observing the LiveData. To manually set the value of a LiveData object, you will have to use a different class called MutableLiveData . MutableLiveData inherits LiveData, here is the class from SDK.

public class MutableLiveData<T> extends LiveData<T> { @Override public void postValue(T value) { super.postValue(value); } @Override public void setValue(T value) { super.setValue(value); } } 1 2 3 4 5 6 7 8 9 10 11 public class MutableLiveData < T > extends LiveData < T > { @ Override public void postValue ( T value ) { super . postValue ( value ) ; } @ Override public void setValue ( T value ) { super . setValue ( value ) ; } }

It provides you with two functions, setValue and postValue , you should call setValue if you are updating the value from MainThread, if you are updating the value from a different thread, then you must use postValue .

Now code with me! In your MainActivity layout file, create a TextView and a Button.

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical" tools:context="com.example.gurleensethi.playground.MobileApplicationEngineeringJava.LiveDataActivity"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Random"/> </LinearLayout> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <? xml version = "1.0" encoding = "utf-8" ?> < LinearLayout xmlns : android = "http://schemas.android.com/apk/res/android" xmlns : app = "http://schemas.android.com/apk/res-auto" xmlns : tools = "http://schemas.android.com/tools" android : layout_width = "match_parent" android : layout_height = "match_parent" android : gravity = "center" android : orientation = "vertical" tools : context = "com.example.gurleensethi.playground.MobileApplicationEngineeringJava.LiveDataActivity" > < TextView android : id = "@+id/textView" android : layout_width = "wrap_content" android : layout_height = "wrap_content" / > < Button android : id = "@+id/button" android : layout_width = "wrap_content" android : layout_height = "wrap_content" android : text = "Random" / > < / LinearLayout >

Get a reference to the TextView and Button in your Java file.

private Button mButton; private TextView mTextView; @Override protected void onCreate(Bundle savedInstanceState) { mTextView = (TextView) findViewById(R.id.textView); mButton = (Button) findViewById(R.id.button); } 1 2 3 4 5 6 7 8 private Button mButton ; private TextView mTextView ; @ Override protected void onCreate ( Bundle savedInstanceState ) { mTextView = ( TextView ) findViewById ( R . id . textView ) ; mButton = ( Button ) findViewById ( R . id . button ) ; }

Create a new MutableLiveData that holds data of type String .

private Button mButton; private TextView mTextView; private MutableLiveData<String> mMutableLiveData; @Override protected void onCreate(Bundle savedInstanceState) { mTextView = (TextView) findViewById(R.id.textView); mButton = (Button) findViewById(R.id.button); mMutableLiveData = new MutableLiveData<>(); } 1 2 3 4 5 6 7 8 9 10 private Button mButton ; private TextView mTextView ; private MutableLiveData < String > mMutableLiveData ; @ Override protected void onCreate ( Bundle savedInstanceState ) { mTextView = ( TextView ) findViewById ( R . id . textView ) ; mButton = ( Button ) findViewById ( R . id . button ) ; mMutableLiveData = new MutableLiveData <> ( ) ; }

Now lets observe the MutableLiveData we created for changes, whenever the value contained by this MutableLiveData changes, we will set the contained value to our TextView.

mMutableLiveData.observe(this, new Observer<String>() { @Override public void onChanged(@Nullable String s) { mTextView.setText(s); } }); 1 2 3 4 5 6 mMutableLiveData . observe ( this , new Observer < String > ( ) { @ Override public void onChanged ( @ Nullable String s ) { mTextView . setText ( s ) ; } } ) ;

We call the method observe on our MutableLiveData which takes two parameters, LifecycleOwner and a Observer. We passed our activity in the first parameter as we already know that our activity implements LifecycleOwner interface.

Now lets change the value of MutableLiveData manually. We will assign a new random string to MutableLiveData whenever the Button is clicked.

mButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Random random = new Random(); mMutableLiveData.setValue("Random Int: " + random.nextInt()); } }); 1 2 3 4 5 6 7 mButton . setOnClickListener ( new View . OnClickListener ( ) { @ Override public void onClick ( View v ) { Random random = new Random ( ) ; mMutableLiveData . setValue ( "Random Int: " + random . nextInt ( ) ) ; } } ) ;

Whenever you press the Button you will see that the value in TextView changes.

Why would I do that?

Now I can suppose you might be thinking that we could have assigned the value directly to TextView rather than doing all this hopping with LiveData. The advantage with LiveData is that when your activity goes into any state other than STARTED or RESUMED it will not call the onChanged method on the observer. Suppose that the String that LiveData got was from a network call, now if your activity gets destroyed before the result from network call is received LiveData will not dispatch the result and would prevent any crashes. Sweet!



Extending LiveData

You can also create LiveData by extending the LiveData class. By doing this you will be able to change the value only from within the class and not from outside of it.

Lets say we have a RealtimeDataSource that dispatches an Integer value after random intervals and we want to observe this emitted value. We will wrap our RealtimeDataSource class in a LiveData class.

class CustomLiveData extends LiveData<Integer> { private RealtimeDataSource realtimeData; private DataListener dataListener = new DataListener() { @Override public void onNewData(Integer data) { setValue(data); } }; public CustomLiveData(RealtimeDataSource realtimeData) { this.realtimeData = realtimeData; } @Override protected void onActive() { super.onActive(); realtimeData.addListener(dataListener); } @Override protected void onInactive() { super.onInactive(); realtimeData.removeListener(dataListener); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class CustomLiveData extends LiveData < Integer > { private RealtimeDataSource realtimeData ; private DataListener dataListener = new DataListener ( ) { @ Override public void onNewData ( Integer data ) { setValue ( data ) ; } } ; public CustomLiveData ( RealtimeDataSource realtimeData ) { this . realtimeData = realtimeData ; } @ Override protected void onActive ( ) { super . onActive ( ) ; realtimeData . addListener ( dataListener ) ; } @ Override protected void onInactive ( ) { super . onInactive ( ) ; realtimeData . removeListener ( dataListener ) ; } }

We add a listener to the RealtimeDataSource and use the setValue method provided by LiveData to update the value.

As you can see we have overridden two functions onActive() and onInactive() . onActive is called when the LiveData has an active observer. onInactive is called when the LiveData has no active observers. Obviously to save network resources we only want to listen to the Realtime data when we have some active observer listening for it. So we register the listener in onActive and remove it in onInactive.

Merging result from different LiveData into one

You can merge results from different LiveData into one using MediatorLiveData . Suppose we have two LiveData objects emitting the value of a certain stock. One LiveData objects emits values from a particular source and the other from a different source. Now we want listen to the result from both of them and also want to neglect the result from second LiveData if it is less than 10.

final MediatorLiveData<Integer> mediatorLiveData = new MediatorLiveData<>(); mediatorLiveData.addSource(stockLiveData, new Observer<Integer>() { @Override public void onChanged(@Nullable Integer value) { mediatorLiveData.setValue(value); } }); mediatorLiveData.addSource(stockLiveDataXYZ, new Observer<Integer>() { @Override public void onChanged(@Nullable Integer value) { if (value > 10) { mMutableLiveData.setValue(value); } } }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 final MediatorLiveData < Integer > mediatorLiveData = new MediatorLiveData <> ( ) ; mediatorLiveData . addSource ( stockLiveData , new Observer < Integer > ( ) { @ Override public void onChanged ( @ Nullable Integer value ) { mediatorLiveData . setValue ( value ) ; } } ) ; mediatorLiveData . addSource ( stockLiveDataXYZ , new Observer < Integer > ( ) { @ Override public void onChanged ( @ Nullable Integer value ) { if ( value > 10 ) { mMutableLiveData . setValue ( value ) ; } } } ) ;

MediatorLiveData takes in two parameters, first is the LiveData which you want MediatorLiveData to observe, second is the callback that will be triggered when the data in LiveData(passed in the first parameter) changes. You can see above we are listening changes to two different LiveData objects.

We also need to set the data to MediatorLiveData so that the Observers listening to it can be notified. Observing MediatorLiveData is same as LiveData.

mediatorLiveData.observe(this, new Observer<Integer>() { @Override public void onChanged(@Nullable Integer integer) { //Use the value } }); 1 2 3 4 5 6 mediatorLiveData . observe ( this , new Observer < Integer > ( ) { @ Override public void onChanged ( @ Nullable Integer integer ) { //Use the value } } ) ;

By doing this you will receive results from different LiveData in the same callback. You can also use MediatorLiveData for the purpose of intercepting results from LiveData.

Transformations

Transformations class provide you with functions with which you can change the value in your LiveData object. Currently only 2 functions are provided in this class, map and switchMap .

map() allows you to apply changes to each value as they are emitted by the LiveData. Here is an example from the docs where a User object is mapped to get the compete name.

LiveData<User> userLiveData = ...; LiveData<String> userName = Transformations.map(userLiveData, user -> { user.name + " " + user.lastName }); 1 2 3 4 LiveData < User > userLiveData = . . . ; LiveData < String > userName = Transformations . map ( userLiveData , user -> { user . name + " " + user . lastName } ) ;

swithMap returns a new LiveData object rather than a value, i.e. it switches the actual LiveData for a new one. Here is an example from official docs, where a userId is switched to get the User object corresponding to the ID.

LiveData<String> userId = ...; LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) ); 1 2 LiveData < String > userId = . . . ; LiveData < User > user = Transformations . switchMap ( userId , id -> getUser ( id ) ) ;

That is it for LiveData, now I would urge you to go and read the official docs.

Understanding Lifecycle in Android Architecture Components

How to use Room in Android – All you need to know to get started

How to make Bottom Sheet in Android

Adding 360 photo viewer in you Android App