LiveData

LiveData under the hood

Alex Zhukovich 7 min read
LiveData under the hood
Table of Contents

LiveData related articles

LiveData is one of the most popular components of the Android Jetpack family. Right now, many modern Android applications use LiveData in the "UI" and sometimes in "Data" layers. I want to demonstrate how the LiveData component works internally.

The LiveData<T> is an observable lifecycle-aware data holder class. This means that LiveData respects the lifecycle of LifecycleOwners, like Activity, Fragment, etc.

If you are not familiar with LiveData, I recommend starting with the official tutorial.

Lifecycle awareness of LiveData

We can observe the value of the LiveData object using any of the following methods:

  • void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer)
  • void observeForever(@NonNull Observer<? super T> observer)

The main difference between these methods is that the observeForever method doesn't have a LifecycleOwner parameter. This means that it is not lifecycle-aware and we need to manually unsubscribe to avoid memory leaks. We can do this using the removeObserver method.

Let's explore available lifecycle owners for Android applications. I want to start with a LifecycleOwner interface.

public interface LifecycleOwner {
   /**
    * Returns the Lifecycle of the provider.
    * @return The lifecycle of the provider.
    */
   @NonNull
   Lifecycle getLifecycle();
}

We can find the most popular implementations of the LifecycleOwner interface in the following classes from the AndroidX library:

  • AppCompatActivity
  • Fragment
  • LifecycleService
  • ProcessLifecycleOwner

We have many additional lifecycle owners that extend one of these classes, like a BottomSheetDialogFragment.

To summarise lifecycle awareness, we need to remember that only the observe method is lifecycle aware. However, you can find many examples of Android projects on GitHub when the observeForever method is used not only in the test code.

LiveData API

The LiveData<T> doesn't support a lot of operations, like Observable<T> or Flow<T> but still, we can do the basic functions. In this article, I will concentrate on the operations connected with observing and changing data.

Updating data:

  • postValue - posts a task from a worker thread to set a given value
  • setValue - sets a given value from the main thread
    Observing data:
  • observe - adds the given observer to the observer list within the lifespan of the given owner; this is a lifecycle-aware method, and we don't need to use removeObserver because it will be done automatically before LifecycleOwner is destroyed
  • observeForever - adds the given observer to the observer list, but we need to use the removeObserver method to avoid memory leaks
  • removeObserver - removes the given observer from the observer list
    Getting data:
  • getValue - gets the current value; the value can be null

I want to introduce the LifecycleBoundObserver class before exploring the internal work of LiveData's methods. The main idea of the LifecycleBoundObserver class is to handle state changes of the LifecycleOwner.

class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
    @NonNull
    final LifecycleOwner mOwner;

    LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {
       super(observer);
       mOwner = owner;
    }

    @Override
    public void onStateChanged(
       @NonNull LifecycleOwner source,
       @NonNull Lifecycle.Event event
    ) {
       Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
       if (currentState == DESTROYED) {
          removeObserver(mObserver);
          return;
       }
       Lifecycle.State prevState = null;
       while (prevState != currentState) {
          prevState = currentState;
          activeStateChanged(shouldBeActive());
          currentState = mOwner.getLifecycle().getCurrentState();
       }
    }

    ...
}

This implementation shows how the changing of lifecycle state is handled. We will talk more about this implementation in the following sections.

Subscribing

Let's start with the internal work of the observe method because one of the parameters in this method is the LifecycleOwner and it adds an observer to the observer list. The following code demonstrates it:

@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
    ...

    LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
    ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
    if (existing != null && !existing.isAttachedTo(owner)) {
       throw new IllegalArgumentException("Cannot add the same observer"
               + " with different lifecycles");
    }
    if (existing != null) {
       return;
    }
    owner.getLifecycle().addObserver(wrapper);
}

In the beginning, the object of LifecycleBoundObserver type is created, and later on, it will be added to the observer list of LifecycleOwner, like Activity, Fragment, etc.

The behavior of the observeForever is different and adds the observer to the observer list of LiveData. We will talk more about it in the "observeForever" section.

Unsubscribing

The removeObserver method allows us to remove the existing observer. This method is needed for cases when you use the observeForever method. I recommend avoiding this method when possible.

The main idea of this method is to remove the observer from the observer list of the LiveData object.

@MainThread
public void removeObserver(@NonNull final Observer<? super T> observer) {
   assertMainThread("removeObserver");
   ObserverWrapper removed = mObservers.remove(observer);
      if (removed == null) {
         return;
   }
   removed.detachObserver();
   removed.activeStateChanged(false);
}

When the LifecycleOwner is in the DESTROY state, the LiveData object will be unsubscribed from the LifecycleOwner. We can see this in the implementation of the LifecycleBoundObserver class:

class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
    @NonNull
    final LifecycleOwner mOwner;

    LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {
       super(observer);
       mOwner = owner;
    }

    @Override
    boolean shouldBeActive() {
       return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
    }

    @Override
    public void onStateChanged(
       @NonNull LifecycleOwner source,
       @NonNull Lifecycle.Event event
    ) {
       Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
       if (currentState == DESTROYED) {
          removeObserver(mObserver);
          return;
       }
       Lifecycle.State prevState = null;
       while (prevState != currentState) {
          prevState = currentState;
          activeStateChanged(shouldBeActive());
          currentState = mOwner.getLifecycle().getCurrentState();
       }
    }

    @Override
    boolean isAttachedTo(LifecycleOwner owner) {
       return mOwner == owner;
    }

    @Override
    void detachObserver() {
       mOwner.getLifecycle().removeObserver(this);
    }
}

The removeObserver method removes a given observer from the observer list of the LiveData object.

Changing data

The LiveData<T> is a data holder class, and we store data of the T type inside the LiveData object. As we discussed before, we can observe data changes in the LiveData object using the observe or observeForever methods.

Let's take a look at the internal implementation of available methods for changing data inside the LiveData object.

Every LiveData object has a mVersion property, which helps to notify all observers on the observer list of the LiveData object. The initial version is equal to -1 or 0; this depends on the constructor, which is used to create the LiveData object.

public abstract class LiveData<T> {
   static final int START_VERSION = -1;

   private int mVersion;
   ...

   public LiveData(T value) {
      mData = value;
      mVersion = START_VERSION + 1;
   }

   public LiveData() {
      mData = NOT_SET;
      mVersion = START_VERSION;
   }
}

The "setValue" method

The void setValue(T value) method sets the LiveData object’s value from the main thread.

The "setValue" method

In the beginning, the function checks that we want to set a value from the main thread. The next step is to update the version of the data and the data itself. Afterward, all observers on the observer list get new data.

The "postValue" method

The void postValue(T value) method allows us to update data from a worker thread by posting value to the main thread and updating data in the LiveData object.

The "postValue" method

The postValue is similar to the setValue method, but has one additional step for posting data from the worker thread to the main thread; this means that data will always be updated from the main thread, and it makes the LiveData not the ideal structure for the "Data" layers.

Observing and getting data

The data inside the LiveData object can be updated by the setValue or postValue methods. We can observe changes in the object use of the following methods:

  • void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer)
  • void observeForever(@NonNull Observer<? super T> observer)

In addition to this, we can get the current value using the “T getValue()” method.

The "observe" method

The observe method adds an observer to the observer list within the lifespan of the specific LifecycleOwner.

The "observe" method

Before adding an observer to the observer list, the following checks should be done:

  • the observe method is called from the main thread
  • the LifecycleOwner is not destroyed
  • the observer is attached to similar lifecycle owners; this means that we cannot use the same observer for different lifecycle owners

When all checks are done, the observer will be added to the observer list.

The "observeForever" method

The observeForever method adds an observer to the observer list; this method is independent from the LifecycleOwner and so we should manually unregister it before destroying the LifecycleOwner.

The "observeForever" method

Before adding "always active observer" (new AlwaysActiveObserver(observer)) to the observer list we have, the implementation of the observeForever function should go through a few checks:

  • the observeForever method is called from the main thread
  • the observer is attached to similar lifecycle owners; this means that we cannot use the same observer for different lifecycle owners

The "getValue" method

The T getValue() returns the current value of the LiveData object. This method can return a null value if the LiveDatahas no values yet.

Let's check the source code of this method:

public abstract class LiveData<T> {
   static final Object NOT_SET = new Object();   

   public LiveData() {
      mData = NOT_SET;
      mVersion = START_VERSION;
   }

   ...

   @Nullable
   public T getValue() {
      Object data = mData;
      if (data != NOT_SET) {
         return (T) data;
      }
      return null;
   }
}

Summary

The LiveData<T> is a commonly used structure for providing data to the “UI” layer. This structure is inefficient in the "Data" layer because the postValue and setValue methods update the LiveData object from the main thread even if we call the postValue from the worker thread.

We can observe LiveData's changes to use the observe or observeForever methods. The observe method is lifecycle aware, while the observeForever is not, and we should remember to call removeObserver before a destroying lifecycle owner, like an Activity or Fragment.


Mobile development with Alex

A blog about Android development & testing, Best Practices, Tips and Tricks

Share
More from Mobile development with Alex

Great! You’ve successfully signed up.

Welcome back! You've successfully signed in.

You've successfully subscribed to Mobile development with Alex.

Success! Check your email for magic link to sign-in.

Success! Your billing info has been updated.

Your billing was not updated.