preface

This article is translated from “Understanding LiveData Made simple” and introduces the use of LiveData in detail. Thanks to Elye. The level is limited, welcome correction discussion. Architecture Components is one of Google’s great perks for Android developers. LiveData is one of the main components. Let’s take a look at how to use LiveData well. If you haven’t seen Architecture Components before, check out another article by the author: Android Architecture Components for Dummies in Kotlin (50 lines of Code). In the previous article, the author mentioned viewModels and LiveData, where LiveData is used to pass data from the ViewModel layer to the View layer. However, the functions of LiveData were not fully introduced at that time. Now let’s look at the definition and use of LiveData in detail. So what’s so special about LiveData?

The body of the

What is a LiveData

The official definition is:

LiveData is an observable data holding class. Unlike ordinary observables (such as an Observable in RxJava), LiveData is life-cycle aware, that is, it can sense the life cycle of other application components (activities, fragments, services). This awareness ensures that only components in the active state receive LiveData updates. Lifecycle can be viewed for details.

This is the official definition of LiveData. For the sake of simplicity, let’s look at some previous development habits to better understand them.

The origin of

When Android was first born, most developers wrote code in an Activity.

However, having all of your logic in one Activity class is not ideal. Because activities are difficult to unit test. In view of this, MVC, MVP, MVVM and other development architectures have emerged, which use Controller, Presenter, ViewModel, and other layers to separate the code from the Activity.

This architecture separates the logic from the View layer. The problem with this, however, is that controllers, Presenters, viewModels, and the like cannot sense the Activity’s life cycle, and must be notified of it. To unify the solution, Google began to pay attention to this problem, and Architecture Components was born. One of the special capabilities of the ViewModel components is that we can sense the Activity’s life cycle without manually notifying them. That’s what the system does internally for us.

In addition to viewModels, the data used to expose from the ViewModel layer to the View layer also has lifecycle awareness capabilities, which is why it is called LiveData. As an observed, it can perceive the life cycle of its Activity.

For example

To better understand, the following diagram shows LiveData as a data center:

As you can see from the figure above, the data source for LiveData is usually the ViewModel, or other components used to update LiveData. Once data is updated, LiveData notifies all its observers, such as components such as activities, fragments, and services. However, unlike other rXJava-like approaches, LiveData does not blindly notify all observers, but checks their real-time status first. LiveData will only notify observers in Actie and will not notify an observer if Paused or Destroyed is in the state. The advantage of this is that we do not need to unsubscribe/observe LiveData in onPause or onDestroy. In addition, once the observer resumes its Resumed state, it will receive the latest data from LiveData again.

A subclass of LiveData

LiveData is an abstract class that we can’t use directly. Fortunately, Google has provided some simple implementations for us to use.

MutableLiveData

MutableLiveData is the simplest implementation of LiveData, which can receive data updates and notify the observer. Such as:

// Declaring it
val liveDataA = MutableLiveData<String>()

// Trigger the value change
liveDataA.value = someValue

// Optionally, one could use liveDataA.postValue(value)
// to get it set on the UI thread
Copy the code

Observing LiveData is also easy. Subscribing to LiveDataA in a Fragment is shown below:

class MutableLiveDataFragment : Fragment() { private val changeObserver = Observer<String> { value -> value? .let { txt_fragment.text = it } } override fun onAttach(context: Context?) { super.onAttach(context) getLiveDataA().observe(this, changeObserver) } // .. some other Fragment specific code .. }Copy the code

As a result, the Fragment receives updates whenever LiveDataA data changes (for example, 7567 and 6269).

The code above has this line:

getLiveDataA().observe(this, changeObserver)
Copy the code

This is where you subscribe to LiveData, but not unsubscribe at Fragment pausing or terminating. Even if we hadn’t unsubscribed, it wouldn’t have been a problem. Looking at the following example, when a Fragment is destroyed, LiveData does not Crash because it generates a new piece of data (1428) to notify Inactive’s Fragment.

Also, you can see that when the Fragment is active again, it will receive the latest LiveData data: 1428.

Transformations#map()

We typically define a Repository to fetch data from a network or database, and may need to do some processing before passing the data to the View layer. As shown below, we use LiveData to transfer data between layers:

We can use the Transformations#map() method to pass data from one LiveData to another.

class TransformationMapFragment : Fragment() { private val changeObserver = Observer<String> { value -> value? .let { txt_fragment.text = it } } override fun onAttach(context: Context?) { super.onAttach(context) val transformedLiveData = Transformations.map( getLiveDataA()) {"A:$it" }
        transformedLiveData.observe(this, changeObserver)
    }
    // .. some other Fragment specific code ..
}
Copy the code

The result is shown below. The code above changes LiveDataA data (5116) to A:5116 after processing.

Using Transformations#map() helps to ensure that LiveData data is not passed to viewModels and Views in a dead state.

It’s cool. We don’t have to worry about unsubscribing. Transformations#map()

@MainThread
public static <X, Y> LiveData<Y> map(@NonNull LiveData<X> source,
        @NonNull final Function<X, Y> func) {
    final MediatorLiveData<Y> result = new MediatorLiveData<>();
    result.addSource(source, new Observer<X>() { @Override public void onChanged(@Nullable X x) { result.setValue(func.apply(x)); }});return result;
}
Copy the code

Another subclass of LiveData, MediatorLiveData, is used here. Now let’s see what this is.

MediatorLiveData

MediatorLiveData has a MediatorLiveData#addSource() method that changes the content of the data. That is, we can use MediatorLiveData to aggregate multiple LiveData sources, as shown below:

The code is as follows:

class MediatorLiveDataFragment : Fragment() { private val changeObserver = Observer<String> { value -> value? .let { txt_fragment.text = it } } override fun onAttach(context: Context?) { super.onAttach(context) val mediatorLiveData = MediatorLiveData<String>() mediatorLiveData.addSource(getliveDataA()) {  mediatorLiveData.value ="A:$it" }
        mediatorLiveData.addSource(getliveDataB())
              { mediatorLiveData.value = "B:$it" }
        mediatorLiveData.observe(this, changeObserver)
    }
    // .. some other Fragment specific code ..
}
Copy the code

In this way, the Fragment can receive both LiveDataA and LiveDataB data changes, as shown in the following figure:

One thing to note: If LiveDataA and LiveDataB data have changed when the Fragment is no longer active, then when the Fragment is active again, MediatorLiveData sends data to the Fragment that fetched the last added LiveData, in this case LiveDataB.

As you can see in the figure above, when the Fragment is active again, it receives the latest data from LiveDataB, whether LiveDataB changed earlier or later than LiveDataA. As you can see from the code above, this is because LiveDataB was the last to be added to MediatorLiveData.

Transformations#switchMap

The example above shows that we can listen for data changes in two LiveData at the same time, which is useful. But what if we want to manually monitor only one of these data changes, and be able to switch as needed? The answer is: Transformations#switchMap(), which Google has provided for us. It is defined as follows:

@MainThread
public static <X, Y> LiveData<Y> switchMap(@NonNull LiveData<X> trigger,
        @NonNull final Function<X, LiveData<Y>> func) {
    final MediatorLiveData<Y> result = new MediatorLiveData<>();
    result.addSource(trigger, new Observer<X>() {
        LiveData<Y> mSource;

        @Override
        public void onChanged(@Nullable X x) {
            LiveData<Y> newLiveData = func.apply(x);
            if (mSource == newLiveData) {
                return;
            }
            if(mSource ! = null) { result.removeSource(mSource); } mSource = newLiveData;if(mSource ! = null) { result.addSource(mSource, new Observer<Y>() { @Override public void onChanged(@Nullable Y y) { result.setValue(y); }}); }}});return result;
}
Copy the code

This method is used to add a new data source and delete the previous one accordingly. So MediatorLiveData will contain only one LiveData source. This control switch is also a LiveData. The whole process is as follows:

The usage method is as follows:

class TransformationSwitchMapFragment : Fragment() { private val changeObserver = Observer<String> { value -> value? .let { txt_fragment.text = it } } override fun onAttach(context: Context?) { super.onAttach(context) val transformSwitchedLiveData = Transformations.switchMap(getLiveDataSwitch()) { switchToB ->if (switchToB) {
                     getLiveDataB()
                } else {
                     getLiveDataA()
                }
        }

        transformSwitchedLiveData.observe(this, changeObserver)
    }
    // .. some other Fragment specific code ..
}
Copy the code

This way, we can easily control which data is used to update the View, as shown below. The Fragment receives updates whenever the LiveData under observation changes or when the LiveData under observation is switched.

A practical usage scenario is that we can handle different business logic through different data sources in a specific setting, such as a user login session.

The source address

The above sample code can be found on the author’s Github: github.com/elye/demo_a… . Download the source code to view, to better understand.

More examples

LiveData with SnackBar, Navigation and Other Events (The SingleLiveEvent Case).

reference

  • Understanding LiveData made simple
  • LiveData with SnackBar, Navigation and other events (the SingleLiveEvent case)
  • LiveData
  • MediatorLiveData
  • MutableLiveData
  • Transformations

contact

I’m Xiaobailong24, you can find me through the following platforms:

  • Making: github.com/xiaobailong…
  • Jane: www.jianshu.com/u/3dac2ad17…
  • The Denver nuggets: juejin. Cn/user / 310467…