ZOFTINO.COM android and web dev tutorials

Android LiveData & Examples

LiveData is provided as a part of android architectural components. LiveData is an observable which can hold and emit data to the subscribed observers. LiveData is similar to RxJava observable with one difference, LiveData is lifecycle aware, this feature allows it to clean up references when the lifecycle state of the component that observer associated with is destroyed.

Android architectural components allow you to build lifecycle aware components. LiveData is a lifecycle aware observable. When observer subscribes to LiveData, lifecycle of the component that observer belong to, is passed to LiveData. As long as the component is active, observer will receive data from LiveData.

In this article, I’ll explain live data in detail with examples.

Add Libraries

To use LiveData, you need to add libraries related to architectural components to your project. Since these libraries are stored in google maven repository, first we need to add this repository to project build.gradle file as shown below.

allprojects {
    repositories {
        jcenter()
        maven { url 'https://maven.google.com' }
    }
}

Add below dependencies to module build.gradle file.

compile 'android.arch.lifecycle:runtime:1.0.0-alpha3'
compile 'android.arch.lifecycle:extensions:1.0.0-alpha3'

Android LiveData Advantages

LiveData holds data and emits the data to observers. Whenever the data that LiveData holds changes, LiveData will emit the latest value to its observers. In your app, if there is a feature that depends on certain data which changes frequently, then you can implement the feature using LiveData.

One main point that needs to be noted is that LiveData is lifecycle aware so it can clean references to observers when the lifecycle of the component, activity or fragment, that observer is associated with is destroyed, that means LiveData doesn’t cause memory leaks. So, you don’t need to worry about the code related to starting and stopping observers in response to the lifecycle events.

And also, LiveData has callback methods onActive and onInactive which get called depending on the number of observers added to LiveData and lifecycle states, when number of observers change from 0 to 1, onActive method is called and when the number of observers change from 1 to 0 onInactive method is called. These callback methods can be used to free up resource-intensive objects when component becomes inactive and recreate them when component becomes active.

When a task needs to be run in the background and UI needs to be updated with the results returned from background task, AsyncTask used to be the solution. But the problem with AsyncTask is that since it is tightly coupled to activity class, activity can’t be destroyed in response to life cycle events when AsyncTask is running. By using LiveData, you can solve the problem of memory leaks which can occur with AsyncTask.

Using Android LiveData

Below are the steps to use LiveData in android apps.

  • First create a data model class which holds data.
  • Create your repository class which gets data from either local data base or remote service and returns LiveData object which holds data of data model class type. Value can be set to LiveData using setValue method in main thread and postValue method from non-main thread.
  • You need to identify event which requires a call to repository to get data, the event can be user events like button click or system events from system service like location manager.
  • You need to identify an event which starts observing data from LiveData by adding observer to LiveData passing lifecycle. Usually, observer is added to LiveData in onCreate method of activity.
  • Observer gets called whenever data is changed, and observer gets removed when lifecycle is in destroyed state.

Android LiveData Example

In the below example LiveData is used to hold time and whenever a button on the screen is clicked, LiveData is updated with the latest time. Observer is added to live data, which updates UI with latest time.

Below code creates and sets value to LiveData.

 public class DataRepository {
    private static MutableLiveData<Long> data = new MutableLiveData<Long>();

    //sets latest time to LiveData
    public static LiveData<Long> getData(){
        data.setValue(new Date().getTime());
        return data;
    }
}
 

Observer is added to LiveData in onCreate method and onclick of a button, latest time is set to LiveData. Observer updates the UI with latest time.

 public class MainActivity extends LifecycleActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //add observer to LiveData
        //observer gets called every time data changes in LiveData
        DataRepository.getData().observe(this, time -> {
            ((TextView)findViewById(R.id.time_t)).setText(""+time);
        });
    }
    //on click of button, set latest time to LiveData
    public void getTime(View view){
        DataRepository.getData();
    }
}
 

Android LiveData Retrofit Example

With the below example, I want to show how retrofit and LiveData can be used together and how to update UI from background thread using LiveData without needing to write clean up code to prevent memory leaks.

In this example, rest service call is made asynchronously using retrofit to get data from server, then, in the background thread, the response is set to LiveData and finally observer updates the UI, in the android main thread, with the data from the response.

In retrofit asynchronous call, http request is made on the background thread, but Callback code is run on android main thread. Since I want to show how to update UI from background thread using LiveData, we need to make retrofit run callback on background thread by setting callbackExecutor on retrofit builder.

To set value on LiveData from background thread, we need to use postValue method, not setValue. If you use setValue from background thread, you will get exception: “ java.lang.IllegalStateException: Cannot invoke setValue on a background thread “.

This example requires INTERNET permission and below dependencies.

compile 'com.squareup.retrofit2:retrofit:2.3.0'
 compile 'com.squareup.retrofit2:converter-gson:2.3.0' 

Retrofit API

 package com.zoftino.livedata;

import retrofit2.Call;
import retrofit2.http.GET;

public interface StoreApi {
    @GET("storeOffers/")
    Call<StoreInfo> getStoreInfo();
}
 

Model Class

 public class StoreInfo {
    private String store;


    public String getStore() {
        return store;
    }

    public void setStore(String store) {
        this.store = store;
    }
}
 

Retrofit Service

package com.zoftino.livedata;


import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.util.Log;

import java.util.concurrent.Executors;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class RetrofitRepository {

    public static final String BASE_URL = "http://www.zoftino.com/api/";
    private static MutableLiveData<StoreInfo> data = new MutableLiveData<StoreInfo>();

    private static Retrofit retrofit = null;

    public static Retrofit getRetrofitClient() {
        if (retrofit == null) {
            retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    //execute call back in background thread
                    .callbackExecutor(Executors.newSingleThreadExecutor())
                    .build();
        }
        return retrofit;
    }

    public static LiveData<StoreInfo> getIntData() {
        return data;
    }

    public static void getStoreInfo() {
        Log.d("", "PROCESSING IN THREAD BEFORE RETROFIT CALL " + Thread.currentThread().getName());
        Call<StoreInfo> call = getRetrofitClient().create(StoreApi.class).getStoreInfo();

        //rest service call runs on background thread and Callback also runs on background thread
        call.enqueue(new Callback<StoreInfo>() {
            @Override
            public void onResponse(Call<StoreInfo> call, Response<StoreInfo> response) {
                StoreInfo si = response.body();
                //use postValue since it is running on background thread.
                data.postValue(si);
                Log.d("", "PROCESSING IN THREAD IN RETROFIT RESPONSE HANDLER " + Thread.currentThread().getName());
            }

            @Override
            public void onFailure(Call<StoreInfo> call, Throwable t) {
                Log.e("", "Error RETROFIT");
            }
        });
    }
}

Activity

public class MainActivity extends LifecycleActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //add observer to LiveData
        //observer gets called when data is set on LiveData
        RetrofitRepository.getIntData().observe(this, storeInfo -> {
            ((TextView)findViewById(R.id.time_t)).setText(""+storeInfo.getStore());
        });
    }
    //on click of a button, set latest data from retrofit to LiveData
    public void getTime(View view){
        RetrofitRepository.getStoreInfo();
    }
}

LiveData with ViewModel

In the above examples, we used static variables for LiveDaata, that means LiveData object will be available till app is closed. We don’t want that, we need LiveData to be available as long as activity or fragment it is associated with exists. To fix this, we need to use ViewModel. ViewModel is lifecycle aware architecture component, that can exist till activity or fragment is destroyed. You can read How to use LiveData and ViewModel with example article.

MediatorLiveData

So far, we learned what LiveData is, what the advantages in using live data are, how to use it in your app, how to use live data with retrofit and how to update LiveData from background thread. In the examples, we used android LiveData implementation MutableLiveData.

One more implementation that comes with the library is MediatorLiveData. MediatorLiveData can be used to decouple observer and LiveData. MediatorLiveData lets you add LiveData and observer to it and it calls the observer when the data that corresponding LiveData holds changes. Using MediatorLiveData you can create LiveData chain to filter, merge, group, and modify data emitted by source LiveData. See Transformation section below for more details.

You can add LiveData to MediatorLiveData by calling addSource method passing LiveData and Observer.

Custom LiveData

You can create custom LiveData by extending LiveData class and implementing abstract methods. Below example shows how to create custom LiveData class called ConnectivityLiveData.

ConnectivityLiveData uses ConnectivityManager to get network information. In onActive method, listener is added to connectivity manager. The listener callback methods are called based on network availability, and the received network data is posted to LiveData. Since these callback methods are called on background thread, we need to use postValue method to set data.

Custom LiveData Example

 package com.zoftino.livedata;


import android.arch.lifecycle.LiveData;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.Network;

//This LiveData emits NetWork information when network availability status changes and there is an active observer to it
public class ConnectivityLiveData extends LiveData<Network> {
    private ConnectivityManager connectivityManager;

    private NetworkCallback listener = new NetworkCallback() {
        @Override
        public void onAvailable(Network network){
            //this part runs on background thread so use postValue
            postValue(network);
        }
        @Override
        public void onLost(Network network){
            postValue(network);
        }
    };

    public ConnectivityLiveData(Context context) {
        //get connectivity system service
        connectivityManager =
                (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
    }

    @Override
    protected void onActive() {
        //onActive is called when there is an active observer to this LiveData
        //since active LiveData observers are there, add network callback listener to connectivity manager
        connectivityManager.registerDefaultNetworkCallback(listener);
    }

    @Override
    protected void onInactive() {
        //onActive is called when there is no active observer to this LiveData
        //as no active observers exist, remove netwrok callback from connectivity manager
        connectivityManager.unregisterNetworkCallback(listener);
    }
}

Activity

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        LiveData<Network> networkData = new ConnectivityLiveData(this);
        networkData.observe(this, network -> {
            ((TextView)findViewById(R.id.time_t)).setText(""+network.describeContents());
        });
    }

LiveData Operators or Transformations

Like RxJava operators which can merge, filter, transform, group and modify observables, transformations can be used to do the same with LiveData. Transformations allow you to operate on data emitted by LiveData before it is passed to observer.

Lifecycle is carried thru the chain of transformations so that transformation and data notification happens only if active observers exist.

There are two transformations provided as part of the lifecycle library, map and switchMap.

Map transformation allows you to apply function on data emitted by source LiveData. Map returns LiveData which emits data returned by the function.

For example, we can apply map on the LiveData from the first example above and modify the value, the resulting live data will emit this modified value.

LiveData<String> userName = Transformations.map(data, currentTime -> {
   return "Current time in m seconds"+ currentTime;
});

SwitchMap transformation is similar to map transformation, it applies function to each value emitted by source LiveData, but function itself returns LiveData.

LiveData<String> userName = Transformations.switchMap(data, currentTime -> {
    MutableLiveData<String> newVal = new MutableLiveData<String>();
     newVal.setValue("current time in m seconds "+currentTime); 
    return  newVal;
});