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.
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
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.
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
.
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.