LiveData introduction

role

Let’s start with the official definition:

LiveData is an observable data store class. Unlike regular observable classes, LiveData has lifecycle awareness, meaning that it follows the life cycle of other application components, such as activities/fragments. This awareness ensures that LiveData updates only application component observers that are in an active lifecycle state.Copy the code

If an Observer (represented by an Observer class) is in a STARTED or RESUMED state during its lifecycle, LiveData considers the Observer to be active. LiveData only notifies active observers of updates. Inactive observers registered to observe LiveData objects do not receive notification of changes.

You can register observers that are paired with objects that implement the LifecycleOwner interface. With this relationship Lifecycle objects can be removed when the state of the corresponding Lifecycle object changes to DESTROYED. This is especially useful for activities and fragments because they can safely observe LiveData objects without fear of leakage (the system unsubscribe them immediately when the Activity and Fragment’s life cycle is destroyed).

advantage

  • Ensure that the interface conforms to data state: LiveData follows observer mode. LiveData notifies the Observer when the underlying data changes. You can integrate code to update the interface in these observers. This way, you don’t have to update the interface every time the application data changes, because the observer does it for you.

  • There are no memory leaks: The observer is bound to the Lifecycle object and cleans itself up after its associated Lifecycle has been destroyed.

  • Does not crash when an Activity stops: If the observer’s life cycle is inactive (such as returning an Activity in the stack), it does not receive any LiveData events.

  • No manual handling of the lifecycle is required: interface components simply observe the relevant data and do not stop or resume the observation. LiveData automatically manages all of these operations because it can sense the associated lifecycle state changes as it observes.

  • Data is always up to date: If the life cycle becomes inactive, it receives the latest data when it becomes active again. For example, an Activity that was once in the background receives the latest data as soon as it returns to the foreground.

  • Appropriate configuration changes: If an Activity or Fragment is recreated due to a configuration change (such as a device rotation), it immediately receives the latest available data.

  • Shared resources: You can use the singleton pattern to extend LiveData objects to encapsulate system services so that they can be shared across applications. The LiveData object is connected to the system service once, and then any observer that needs the corresponding resource only needs to observe the LiveData object.

// This is the official documentation

The use of LiveData

The following is an introduction to the use of LiveData. You can better understand the above content by mastering the use method.

The basic use

Observe () method

  1. Create a LiveData instance, specifying the source data type
  2. Create an Observer instance and implement the onChanged() method to receive source data changes and refresh the UI
  3. The LiveData instance adds an observer using the observe() method and passes in the LifecycleOwner
  4. LiveData instance updates source data with setValue()/postValue() (child thread postValue())

Here’s an example:

class LiveDataTestActivity : AppCompatActivity(){ companion object { private const val TAG = "LiveDataTestActivity" } var mLiveData = MutableLiveData<String>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) mLiveData.observe(this, Observer { Log.i(TAG, "onChanged: "+ it)}) // Activity is inactive and will not be called onChanged. When it becomes active, Mlivedata. value = "onCreate"} override fun onStart() {super.onstart () log. I (TAG, "onStart: "// Active state, onChanged will be called back. Mlivedata. value = "onStart: } override fun onResume() {super.onResume() log. I (TAG, "onResume: ") // Change mliveData. value = "onResume: "} override fun onPause() {super.onpause () log. I (TAG, "onPause: ") // Active status, onChanged mlivedata. value = "onPause: "} override fun onStop() {super.onstop () log. I (TAG, "onStop: ") // Is inactive and does not call onChanged. Mlivedata. value = "onStop: " } override fun onDestroy() { super.onDestroy() Log.i(TAG, "onDestroy: OnChanged mLiveData.value = "onDestroy: "}}Copy the code

Note that the LiveData instance mLiveData is created using MutableLiveData, which is the implementation class of LiveData, and specifies the source data type as String. You then create an instance of the interface Observer and implement its onChanged() method to receive changes to the source data. The Observer calls mLiveData’s observe() method with the Activity as an argument to indicate that the Observer is observing mLiveData. MLiveData’s setValue() method is then called in all lifecycle methods of the Activity.

ObserveForever (Observer) method

Also, in addition to adding an Observer using the observe() method, you can use the observeForever(Observer) method to register observers that are not associated with LifecycleOwner. In this case, the observer is seen to be active at all times.

Here’s an example:

mLiveData.observeForever(Observer {
    Log.i(TAG, "observeForever onChange: " + it)
})
Copy the code

However, the Observer object obtained with observeForever will remain active, requiring a manual call to removeObserver(Observer) to remove the Observer.

Extended use

The extension includes two points:

  • Custom LiveData, overriding its own callback methods: onActive(), onInactive().
  • Implement LiveData as a singleton mode, easy to share data among multiple activities and fragments.

Official examples are as follows:

class StockLiveData(symbol: String) : LiveData<BigDecimal>() {
    private val stockManager: StockManager = StockManager(symbol)

    private val listener = { price: BigDecimal ->
        value = price
    }

    override fun onActive() {
        stockManager.requestPriceUpdates(listener)
    }

    override fun onInactive() {
        stockManager.removeUpdates(listener)
    }

    companion object {
        private lateinit var sInstance: StockLiveData

        @MainThread
        fun get(symbol: String): StockLiveData {
            sInstance = if (::sInstance.isInitialized) sInstance else StockLiveData(symbol)
            return sInstance
        }
    }
}
Copy the code

To observe stock price changes, StockLiveData is custom derived from LiveData, and it is a singleton pattern that can only be obtained through get(String Symbol) method. OnActive () and onInactive() were overwritten and the logic of starting and removing stock price updates was added.

OnActive () is called when the number of active observers (LifecycleOwner) changes from 0 to 1. OnInactive () is called when the LifecycleOwner number changes from 1 to 0.

That is, only when there is an active observer (LifecycleOwner) will it connect to the stock price update service to listen for stock price changes. Use as follows:

class MyFragment : Fragment() {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        StockLiveData.get(symbol).observe(viewLifecycleOwner, Observer<BigDecimal> { price: BigDecimal? ->
            // Update the UI.
        })

    }
Copy the code

Since StockLiveData is in single-instance mode, data can be shared between multiple LifyCycleOwners (Activities, fragments).

Advanced usage

You can use the provided Brace classes if you want to make changes to values stored in LiveData objects before dispatching them to observers, or if you need to return different LiveData instances based on the values of another instance.

Data modification – Minerality.map

Int liveData1 val liveData1 = MutableLiveData<Int>() // Convert to String liveDataMap val liveDataMap = Transformations.map(liveData1) { val s = "$it + Transformations.map"; Log.i(TAG, "apply: " + s); } liveDataMap.observe(this, Observer { Log.i(TAG, "onChanged1: $it"); }) liveData1.setValue(100);Copy the code

It’s easy to use: Instead of adding an observer to the original liveData1, a new liveDataMap is generated by modifying the liveData1 data using the mineral.map () method. Observers are added to liveDataMap and data is set at last.

This example changes the liveData1 of type Integer to a liveDataMap of type String.

Data switch – Transformations. SwitchMap

If you want to switch according to a certain value to observe different LiveData data, you can use Transformations. SwitchMap () method.

// Two liveData, It is up to liveDataSwitch to decide which livaData data to return val liveData3 = MutableLiveData<String>() val liveData4 = MutableLiveData<String>() // Switch condition LiveData, Val liveDataSwitch = MutableLiveData<Boolean>() //liveDataSwitchMap is generated by switchMap(). Val liveDataSwitchMap: LiveData<String> = Transformations.switchMap(liveDataSwitch){ if (it) liveData3 else liveData4 } liveDataSwitchMap.observe(this, Observer { s -> Log.i(TAG, "onChanged2: $s")}) val switchValue = true livedatwitch.setValue (switchValue) // liveData3. SetValue ("liveData3") liveData4.setValue("liveData4")Copy the code

LiveData3 and liveData4 are two data sources. There is a judgment condition to determine which data to fetch. This condition is liveDataSwitch. Transformations. SwitchMap () is used to implement this logic, the return value added liveDataSwitchMap observer.

View multiple data – MediatorLiveData

MediatorLiveData is a subclass of LiveData that allows merging of multiple LiveData sources. Any change to the original LiveData source object triggers the observer of the MediatorLiveData object.

val mediatorLiveData = MediatorLiveData<String>() val liveData5 = MutableLiveData<String>(); Val liveData6 = MutableLiveData < String > () / / add source LiveData mediatorLiveData. AddSource (liveData5) {Log i. (TAG, "onChanged3: $it"); mediatorLiveData.setValue(it); } / / add source LiveData mediatorLiveData. AddSource (liveData6) {Log i. (TAG, "onChanged4: $it"); mediatorLiveData.setValue(it); } mediatorLiveData.observe(this){ Log.i(TAG, "onChanged5: $it"); } liveData5.setValue("liveData5"); liveData6.setValue("liveData6");Copy the code

For example, if the interface has LiveData objects that can be updated from a local database or network, you can add the following source to the MediatorLiveData object:

LiveData5 associated with data stored in a local database liveData6 associated with data accessed from the network

The Activity simply observes the MediatorLiveData object to receive updates from both sources.

Source code analysis

How does LiveData achieve the aforementioned features, such as being able to sense life cycle state changes, and not having to manually remove observations?

Add observer

Livedata.observe () : livedata.observe () :

/** * Add observer. Events are dispatched on the main thread. If LiveData already has data, it will be distributed directly to the Observer. * The observer accepts events only when LifecycleOwner is active and removes the Observer if it is in a DESTROYED state. * The Observer does not receive updates when data is inactive. It automatically receives the latest data when it becomes active. LifecycleOwner holds a strong reference to the Observer and owner when the LifecycleOwner is not in the DESTROYED state. The reference is automatically removed when the LifecycleOwner is in the DESTROYED state. * @param owner Controls the observer LifecycleOwner * @param Observer Observer that receives events */ @mainThread public void observe(@nonNULL LifecycleOwner owner, @NonNull Observer<? Supert > observer) {assertMainThread("observe"); If (owner.getLifecycle().getCurrentState() == DESTROYED) {// LifecycleOwner is DESTROYED; } // Use LifecycleOwner and Observer to assemble LifecycleBoundObserver. Add LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer) to mObservers; // If the mObservers do not contain the key, putIfAbsent returns null. // If the mObservers already contain the key, Calling putIfAbsent does not store a key-value and returns the value ObserverWrapper saved in existing = mObservers. if (existing ! = null && ! existing.isAttachedTo(owner)) { //! Existing. IsAttachedTo (owner) Specifies that the owner specified by the Observer already added to the mObservers is not the owner passed in. Throw new IllegalArgumentException("Cannot add the same observer" + "with different lifecycles"); } if (existing ! = null) { return; } owner.getlifecycle ().addobServer (wrapper);} Owner.getLifecycle (). }Copy the code

LifecycleOwner must be DESTROYED and cannot be added. Then assemble the LifecycleBoundObserver wrapper instance wrapper using LifecycleOwner and Observer. Add a key-value to the observer list mObservers using the putIfAbsent method observer-Wrapper. (putIfAbsent means that the observer is added only if it is not present in the list.) If the observer key already exists in the mObservers but the owner in the value is not the owner passed in, an error message “cannot add the same observer but a different LifecycleOwner” is displayed. If it is the same owner, return directly. Finally Lifecycle of LifecycleOwner adds a wrapper to the Observer. Also, look at the observeForever method:

@MainThread public void observeForever(@NonNull Observer<? super T> observer) { assertMainThread("observeForever"); AlwaysActiveObserver wrapper = new AlwaysActiveObserver(observer); ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper); if (existing instanceof LiveData.LifecycleBoundObserver) { throw new IllegalArgumentException("Cannot add the same observer" + " with different lifecycles"); } if (existing ! = null) { return; } wrapper.activeStateChanged(true); }Copy the code

Similar to observe(), except that the observer is always active,

Event callback

LifecycleBoundObserver

The Observer method corresponds to the Observer LifecycleBoundObserver to see how the callback is performed:

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() {Lifecycle is considered to be active only if Lifecycle is RESUMED or RESUMED mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED); } @Override public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) { if (mOwner.getLifecycle().getCurrentState() == DESTROYED) { removeObserver(mObserver); //LifecycleOwner changes to DESTROYED, removes observer return; } activeStateChanged(shouldBeActive()); } @Override boolean isAttachedTo(LifecycleOwner owner) { return mOwner == owner; } @Override void detachObserver() { mOwner.getLifecycle().removeObserver(this); }}Copy the code

LifecycleBoundObserver is an inner class of LiveData that wraps around the original Observer and binds the LifecycleOwner to the Observer. LifecycleBoundObserver is said to be the active observer when the LifecycleOwner is active. It implements the LifecycleEventObserver interface and implements the onStateChanged method. OnStateChanged as mentioned in Lifecycle is a callback to Lifecycle state changes. Removes the observer when the LifecycleOwner lifecycle state changes if it is in a DESTROYED state. This is where LiveData automatically removes the observer feature. This is one of the most important points LiveData can make to avoid memory leaks. Instead of DESTROYED state, activeStateChanged() method of parent ObserverWrapper will be called to handle the lifecycle state change. ShouldBeActive () is at least true if it is STARTED or true if it is active.

AlwaysActiveObserver

ObserveForever method corresponding to the observer AlwaysActiveObserver,

private class AlwaysActiveObserver extends ObserverWrapper { AlwaysActiveObserver(Observer<? super T> observer) { super(observer); } @override Boolean shouldBeActive() {return true if there are any data changes; }}Copy the code

In line with the LifecycleBoundObserver method, it is also an implementation class of the abstract ObserverWrapper class, whose shouldBeActive() returns a fixed true, meaning that any data changes are called back. So use observeForever() to actively remove the Observer afterwards to avoid memory leaks and NPE.

ObserverWrapper
private abstract class ObserverWrapper { ... void activeStateChanged(boolean newActive) { if (newActive == mActive) { return; // If the active state has not changed, it will not be processed. } mActive = newActive; boolean wasInactive = LiveData.this.mActiveCount == 0; / / there is no active observer LiveData. This. MActiveCount + = mActive? 1:1; //mActive becomes active if (wasInactive && mActive) {onActive(); / / the number of observer active from 0 to 1} the if (LiveData. This. MActiveCount = = 0 &&! mActive) { onInactive(); } if (mActive) {dispatchingValue(this); // When the observer becomes active, data is distributed}}}Copy the code

ObserverWrapper is also an internal class to LiveData. MActive is a property of ObserverWrapper that indicates whether this observer is active. If the active status does not change, no processing is performed. LiveData. This. MActiveCount = = 0 is refers to the number of active observer LiveData. Changing the number of active observers from 0 to 1 and from 1 to 0 calls LiveData’s onActive() and onInactive() methods, respectively. This is the callback method used by the extension mentioned earlier. Finally the observer becomes active and dispatchingValue(observerWrapper) of LiveData is used for data distribution:

dispatchingValue
Void dispatchingValue(@nullable ObserverWrapper initiator) {if (mDispatchingValue) {// If mData is being published to mObservers MDispatchInvalidated = true; mDispatchInvalidated = true; mDispatchInvalidated = true; mDispatchInvalidated = true; // If the distribution is currently underway, the distribution is invalid, return return; } mDispatchingValue = true; Do {mDispatchInvalidated = false; if (initiator ! = null) { considerNotify(initiator); //observerWrapper is not empty, notify the true observer initiator = null using notice (); } else {//observerWrapper is empty, iterating to inform all observers for (Iterator< map.entry <Observer<? super T>, ObserverWrapper>> iterator = mObservers.iteratorWithAdditions(); iterator.hasNext(); ) { considerNotify(iterator.next().getValue()); "If (mDispatchInvalidated) {" if (mDispatchInvalidated) {" if (mDispatchInvalidated) {" if (mDispatchInvalidated) {" if (mDispatchInvalidated) {" if (mDispatchInvalidated) {" if (mDispatchInvalidated) {" if (mDispatchInvalidated) {" if (mDispatchInvalidated) {" } } } } while (mDispatchInvalidated); mDispatchingValue = false; }Copy the code

The dispatchingValue() function is cleverly designed. With two global Boolean variables, mDispatchingValue and mDispatchInvalidated, the logic of judging the old and new values, discarding the old values and re-issuing the new values globally is realized.

If it is currently being distributed, the distribution is invalid; If observerWrapper is not empty, let the true observer be notified using notice (), and if observerWrapper is empty, let all observers be notified by notice.

Note the traversal process of mObservers. Each time an item is traversed, the current value is checked to see if it is obsolete. If so, the traversal is interrupted, so only some observers receive values.

considerNotify
private void considerNotify(ObserverWrapper observer) { if (! observer.mActive) { return; } // If the owner of the current observer is inactive, activeStateChanged will be called again, and false will be passed, internally checking if (! observer.shouldBeActive()) { observer.activeStateChanged(false); return; } if (observer.mLastVersion >= mVersion) { return; } observer.mLastVersion = mVersion; observer.mObserver.onChanged((T) mData); // Call back to the real mObserver onChanged method}Copy the code

Check the status first: return if the observer is inactive; If the owner corresponding to the current Observer is inactive, the activeStateChanged method is called again and passed false, which is evaluated internally again. Finally, the onChanged method of the real mObserver is called with the value of the LivaData variable mData. This is where the callback logic makes sense.

Data update

LivaData data updates can use setValue(value), postValue(value), the difference is that postValue(value) is used for child threads:

//LivaData.java

postValue
Private Final Runnable mPostValueRunnable = new Runnable() {@suppressWarnings ("unchecked") @override private Final Runnable mPostValueRunnable = New Runnable() {@suppressWarnings ("unchecked") @override public void run() { Object newValue; Synchronized (mDataLock) {// Synchronize (mDataLock) {newValue = mPendingData; mPendingData = NOT_SET; } setValue((T) newValue); // also go to setValue method}}; protected void postValue(T value) { boolean postTask; synchronized (mDataLock) { postTask = mPendingData == NOT_SET; mPendingData = value; } // If postTask is false, there is an old value that needs to be called by postValue, because postValue can be called in child threads, The onChanged(value) method of the Observer must be used to send the latest value mPendingData when mPostValueRunnable is actually executed So return if (! postTask) { return; } // Send a runnable to the main thread, mainly to call postValue in the child thread, In the main thread to value the callback ArchTaskExecutor. GetInstance (). PostToMainThread (mPostValueRunnable); }Copy the code

The postValue method throws the Runable object mPostValueRunnable onto the main thread, using setValue() in its run method.

The postValue(T) function is not limited to the caller’s thread, either the main thread or the child thread can be called, so there is the possibility of multi-thread competition. The main point of the postValue(T) function is to understand the state change between the child thread and the main thread.

Since the LiveData callback behavior is fixed to the main thread, the postValue(T) function puts the value callback logic into the Runnable and posts it to the Handler for the main thread to execute. Therefore, there is a time lag between postValue(T) being called and Runnable being executed, at which point another thread may call postValue(T) passing the new value.

Before mPostValueRunnable is executed, all values passed through the postValue(T) function are saved to the variable mPendingData, and only the last one is kept. MPendingData will not be reset until mPostValueRunnable has been executed, so using postValue(T) is possible in cases where multiple threads are called simultaneously or a single thread is called consecutively (the external Observer will only receive the latest value).

setValue

The setValue(T) function is restricted to the main thread only.

@mainThread protected void setValue(T value) {assertMainThread("setValue"); // update the version number of the current value, that is, the degree of new value mVersion++; mData = value; dispatchingValue(null); }Copy the code

SetValue () assigns value to mData and then calls dispatchingValue(NULL) with the null argument corresponding to the previously mentioned observerWrapper null scenario, i.e. the dispatch callback traverses through all observers. Here the complete implementation logic of the observer mode becomes clear: LivaData adds the observer bound to LifecycleOwner via observe(); Callback the latest data when the observer becomes active All observers are notified of callbacks when data is updated using setValue() and postValue().

summary

  • An Observer object can only be bound to a Lifecycle object, otherwise an exception will be thrown
  • Observe () and observeForever() cannot be used in the same Observer; otherwise an exception will be thrown
  • LiveData has the possibility of lost values. When a single thread passes a value consecutively or multiple threads postValue simultaneously, only the last value may eventually be retained and called back
  • There is a possibility that only some observers of LiveData will receive value callbacks. When a single thread transmits values consecutively or at the same time, for example, valueA and valueB are transmitted successively, only some observers may receive valueA, and then all observers may receive valueB