When and why to use Android LiveData

Giora Shevach
ProAndroidDev
Published in
9 min readApr 30, 2018

--

Almost a year ago (first alpha on May 2017), Google released “Android Architecture Components,” a collection of libraries intended to help Android developers design more robust, testable and maintainable apps. Most notable are the LiveData class and the related lifecycle-aware classes, the Room persistence library and the new paging library. In this post I’ll explore the LiveData class, the problems it wishes to solve and when to use it.

Basically, LiveData is an observable data holder. It lets the components in your app, usually the UI, observe LiveData objects for changes.

The new concept about this LiveData is that it’s lifecycle-aware, meaning it respects the lifecycle state of the app components (activities, fragments) and ensures that LiveData only updates the component (the observer) when it’s in an active lifecycle state. This behavior prevents object leaking and ensures the app doesn’t do more work than it should.

To better understand when to use this new observable data holder and the advantages of using it, in the rest of this post I’ll review some of the alternatives to face the fundamental task of updating the UI based on data changes:

  1. Managing data in the UI component
  2. Using a listener interface
  3. Using an event bus
  4. Using LiveData
  5. Summary

But first, let’s present our example scenario:

Scenario

In order to demonstrate with code snippets, imagine we’re building a UI of a screen in a social-network app, which shows a user profile along with the number of followers of that user. Underneath the profile image and the number of current followers, there’s a toggle button which lets the current logged-in user to follow/unfollow that user. We want this button to affect the label with the number of followers and to change the text on the button accordingly. (Java language will be used for code).

#1 — Managing data in the UI component

A naive approach is turning the UI components (activities, fragments) into “God Objects.” You first start with the only UI component that the framework supplies you with, an activity for example, where you write your app’s UI code. Later, when you need to handle data and change the UI upon it, you find that it’s easier to just keep on writing the code in the activity, as it already contains all the fields and UI elements which should be updated. Let’s look at how the code will look:

Besides for it being an anti-pattern and the fact it violates some key principles of writing a well-designed code, this approach has a major flaw when it comes to data and persistence:

Loss of data and state — app components like activities or fragments aren’t managed by us but rather by the system. Because their lifecycle isn’t under our control, they can be destroyed at anytime based on user interactions or other factors like low memory. If we were to create and handle our data in an UI component, all of our data would be destroyed once that component is destroyed. In this example, every time the user rotates the device for example, the activity gets destroyed and recreated again, causing all the data members to reset and the network calls to be executed again, wasting the user bandwidth and forcing the user to wait for the new queries to complete.

#2 — Using a listener interface

An alternative way to solve this task of updating the UI based on data changes is by using listener interfaces, which impose a specific function on the UI listener:

I omitted some interface declarations and some implementations for the sake of brevity. First, let’s go over this solution: The activity itself isn’t aware of the data aspect of the user’s followers. Its only concern is to show a UI with a text and where the user can click a button. Note how the activity now doesn’t contain a single line of if condition. The responsibility of fetching the data (from somewhere) and deciding how the UI will look upon it now belongs to the ProfileController. The controller in turn uses the ProfileRepository to fetch the data, whether from the network (using the WebService that was previously used in the activity) or from somewhere else (like a memory cache or persistence). Once the controller has the data and is ready to update the UI, it calls back the passed in listener (which is actually the activity) and invokes one of its methods. Actually, the ProfileController is the first step towards a MVP design (Model-View-Presenter). We could setup the controller to use a couple more mini-controllers, and each of them would change the corresponding UI element itself, thus extracting the UI-changing functionality out of the activity altogether.

This technique avoids data loss once the UI component is destroyed and is useful for properly separating concerns in the code. Moreover, it keeps the code of the UI components clean and as lean as possible, thus making our code easier to maintain and generally allows us to avoid many lifecycle related problems. The main disadvantage of this valid approach is that it’s somewhat error-prone and you can find yourself causing an exception or a crash if you’re not careful enough. This is a bit hard to demonstrate with this simple example but for more complex and real-world scenarios, errors are bound to happen. For example, if the activity had gone through a configuration change, your listener reference might be null. Another example is when your listener’s lifecycle is inactive, such as in the case of an activity in the back stack, and you’re trying to pass events to it and call its functionality. Generally speaking, this approach requires you to know about the lifecycle of the listeners (UI components) and take it into consideration in your code. This is also the case for languages in which functions are first-class citizens like Kotlin. Although you can pass a function as an argument instead of the UI component itself, here too you should be aware of the lifecycle of the UI component, as the function will usually manipulate UI elements of that component.

#3 — Using an event bus

Another approach is to use an event-based mechanism (publisher/subscribers) when we have to update the UI based on data changes (demonstrated using greenrobot EventBus):

As you can see, this solution is very similar to the previous one. The difference is that instead of invoking the listener’s methods, we fire events. These events are intercepted by the subscribers, in our case the activity, and then the UI is changed accordingly.

There’s a heated discussion within the community whether event bus is the way to go or whether the listener callbacks are truly the solution. Anyway, this technique, as the listener interface, also avoids data loss and keeps the concerns in the code separated. Additionally, libraries implementing the event-based mechanism usually support advanced features like delivery threads and subscribers’ priorities (One can also use Android LocalBroadcast without the need of a 3rd party library). As opposed to the listener interface approach, the event-based approach eliminates the need for you to take lifecycle issues into account as most of the libraries do it for you. Nevertheless, you need to take care of registering (and un-registering) the subscribers so they can receive events, and failing to do so correctly might result in unnoticed memory leaks. In addition, although an event bus might seem convenient to implement at first, it can quickly turn into a mess of complex events all over the code base, which makes it really hard to follow when reviewing or debugging the code.

Another major thing you should look out for when using an event bus has got to do with the one-to-many nature of this mechanism. As opposed to the listener approach, where you only have one subscriber to your event, in the event bus approach you may find yourself with many subscribers, who not all of them you’re aware of. Take for example the case where the user opens two profile pages, both instances of type UserProfileActivity. Then, an event is fired. This event would be received on both instances of the UserProfileActivity, causing one of them to probably be updated incorrectly, as the event originally only corresponds to one of them. This could be a bug. In order to fix it, you may find yourself going through hoops, querying extra event’s properties like the user’s id just to avoid wrong event interceptions.

In my opinion, there is justification for an event bus mechanism, but the situations in which you should use it are very specific. For example, the case of application cross events, where there isn’t a clear relation between the source of the event and the actors upon it. In the case of updating the UI based on a change in the data, such as in our example, I don’t think there’s a reason for using an event bus, and among this approach and the previous one of listener interface, I would choose the latter.

#4 — Using LiveData

After exploring the existing and familiar approaches to face the same task, let’s see how Android Architecture Components’ LiveData solves it:

Alright, so let’s go over the code: The activity retrieves a copy of the ProfileViewModel, which is designed to manage and store UI-related data (or delegate storage to some other class). The great thing about this ViewModel (note that the base class belongs to the Android Architecture Components) is that it’s retained across the lifecycle of the activity, meaning it will exist until the activity goes away permanently, i.e. the activity is finished. Once the ProfileViewModel is retrieved, the activity starts observing for data changes. This is where the magic of LiveData kicks in. The view model returns LiveData, which is an observable class, thus making our activity the observer. Like for the events based solution, when data is changed, we change the UI accordingly. In our example, the view models gets its return value from the UserRepository class, which keeps an instance of LiveData that wraps around a data holder, FollowStatus. I made the repository memory based and not disk persisted for brevity sake. When the user clicks the Follow/Unfollow button, the code calls the view model’s toggleFollowing method, which in turn calls the UserRepository. Once the repository changes the FollowStatus value stored in its LiveData instance, the activity’s onChanged code gets called again, as the activity observes the FollowStatus and waits for changes in the data. This is how the data-change <-> UI change cycle works with LiveData.

The new thing about LiveData is that it’s lifecycle-aware. In our case, it’s aware of the lifecycle of the instance this that we gave it when we started to observe it, meaning the lifecycle of the activity. That means that only when the activity is in an active lifecycle state does the LiveData send an “on changed event”. If for example the activity is in the backstack, it won’t get notified on data changes until it becomes visible to the user again. This means that there will be no more crashes due to stopped activities. And since the LiveData observable takes control of firing the events, there is no need for us to handle lifecycle manually. This ensures that when using LiveData, the UI component is always up to date even when it becomes inactive at some point, as it receives the latest data upon becoming active again.

LiveData is so powerful that some people implemented an event-bus mechanism using LiveData as the basic infrastructure. Additionally, LiveData is also supported by the new SQLite persistence library Room, which was launched as part of the Android Architecture Components. This means that we can save LiveData objects to the database and later observe them as regular LiveData. This lets us save data in one place in our code and have it affect another place in our code, which observes that data. We could extended our UserRepository to use Room and persist the data, but I didn’t want to over-expand the example too much.

Summary

After reviewing the different approaches to solve the same task, we can think of LiveData as a mix between Interface Listeners and the Event-Based solution, taking the good from each solution. As a rule of thumb, I would advise to use (or switch to) LiveData almost in every situation in which these other alternatives are considered (or were already used), and specifically, in every scenario in which we want to update the UI based on data changes in a clean, robust and reasonable manner.

I hope you gained some knowledge about LiveData from this post, understood in which scenarios it can help and how to use it, and why it’s probably a better solution than other existing approaches. Think otherwise? Have a better solution? Feel free to add a comment.

--

--