LiveData components

What is a LiveData

The LiveData component is Jetpack’s basic observer mode message subscription and distribution component with host (Activity/Fragment) life awareness, which ensures that LiveData is only distributed to active observers for the time being

Active: Normally equals the Observer to be in the started state. Of course, if an Observer is registered with observerForever, the Observer is still active

LiveData is distributed only to active observers. This ensures that active observers update data and interfaces in a timely manner, and saves resources occupied by inactive observers in updating data and interfaces in the background. The appearance of LiveData solves the problems of empty pointer, background resource preemption and manual anti-registration brought by previous callback methods

LiveData advantage

  • Ensure that the interface conforms to the data state

    LiveData follows the observer pattern. When the life cycle state changes, LiveData notifies the [Observer] object and sends it the latest data. Instead of updating the interface every time the data changes, the observer can update the interface whenever it receives an onChanged event.

  • You no longer need to handle the life cycle manually

    You only need to observe relevant data. You do not need to manually stop or resume the observation. LiveData automatically manages unregistration of the Observer because it senses changes in the host life cycle and automatically unregisters onDestory in the host life cycle. Therefore, using LiveData for message distribution does not cause memory leaks

  • Data is always up to date

    If the host’s 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.

  • Support distribution of sticky events

    By default, the user can receive the data sent before registering an observer

  • Shared resources

    We can extend LiveData using the singleton pattern to implement a global message distribution bus.


The use of LiveData

Simple to use

LiveData is an abstract class that has a simple implementation class, MutableLiveData

class LiveDataActivity : AppCompatActivity() { private val myHandler = Handler() override fun onCreate(savedInstanceState: Bundle?) {super.oncreate (savedInstanceState) setContentView(r.layout.activity_live_data) testLiveData1()} // Create a LiveData object Private val myLiveData = MutableLiveData<String>() private fun testLiveData1() { {log. I ("LiveDataActivityTag",it)}) // Use postValue to send myLivedata.postValue ("My------------") myHandler.postdelayed ({log. I ("LiveDataActivityTag",it)}) // Use postValue to send myLiveData.postValue("My------------") myHandler.postdelayed ({ / / delay the setting of the 3 s after use setValue myLiveData. Value = "My++ + + + + + + + + + + + + + + + +"}, 3000)}}Copy the code

Results (note the time)

Next, test the effect of sending changes while inactive. Immediately after opening the screen, press the Home button and stay for more than 3 seconds before returning

Results (note the time)This verifies that LiveData is only temporarily not sent to the inactive Observer, and when the Observer returns to the active state, it immediately sends a message to the Observer to update the data in time

Livedata.setvalue () This method call must be in the main thread

Livedata.postvalue () this method can be called in child threads or on the main thread

LiveData operator

Transformations. The map () is used

You can use this operator if you want to make changes to LiveData before it is distributed

class LiveDataActivity : AppCompatActivity() { private val myHandler = Handler() private val myLiveData = MutableLiveData<String>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_live_data) testMap() } private fun testMap() { Mylivedata. observe(this, Observer {// Log. I ("LiveDataActivityTag", MyLiveData = mineraldata = mineraldata = mineraldata = mineraldata = mineraldata LiveData map.observe(this, Observer {log. I ("LiveDataActivityTag", Observer {log. I ("LiveDataActivityTag", Observer {log. I ("LiveDataActivityTag", Observer {log. I ("LiveDataActivityTag", Observer {Log. PostValue (" myLivedata.postValue ("My")) // Delay the second value myHandler.postdelayed ({mylivedata.postValue ("You")}, 3000)}}Copy the code

Results:As in the example above, no data is received while inactive, so I won’t show it here

Transformations. SwitchMap () is used

This operator can be used when you need to switch between different LiveData to view based on certain conditions

class LiveDataActivity : Appactivity () {// define three LiveData, of which myLiveData3 is the switching condition private val myLiveData = MutableLiveData<String>() private val myLiveData2 = MutableLiveData<String>() private val myLiveData3 = MutableLiveData<Boolean>() override fun onCreate(savedInstanceState: Bundle?) {super.oncreate (savedInstanceState) setContentView(r.layout.activity_live_data) testSwitchMap()} One principle, three optimizations: Principle: the interface as a parameter parameter position replacement can be written as follows: {parameter 1, parameter 2... -> {need to run the method}} optimization: 1. When the interface do parameter is in the last parameter, can be mentioned outside the parentheses 2. 3. If the interface callback method has only one argument, the argument and function inference can be omitted. * */ private fun testSwitchMap() {// myLiveData3 as a switching condition for different returns val switchMap = Transformations. SwitchMap (myLiveData3) {input - > if (input) myLiveData else myLiveData2} / / observe the data changes switchMap.observe(this, Observer { Log.i("LiveDataActivityTag", Mylivedata3. postValue(true) myLiveData.postValue("My+++++++") myLiveData2.postValue("You+++++++") } }Copy the code

Since the conditional LiveData is switched to the first LiveData with true, only the data changes of the first one are received, and the second one is not

Merge multiple LiveData MediatorLiveData. AddSource ()

This class is used when you need to listen on multiple LiveData at the same time

   class LiveDataActivity : AppCompatActivity() {
    private val myLiveData = MutableLiveData<String>()
    private val myLiveData2 = MutableLiveData<String>()
    private val myLiveData3 = MutableLiveData<Boolean>()
    private val myLiveData4 = MediatorLiveData<Any>()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_live_data)
        testMediatorLiveData()
    }

    private fun testMediatorLiveData() {
        myLiveData4.addSource(myLiveData, Observer {
            Log.i("LiveTag1", it)
        })
        myLiveData4.addSource(myLiveData2, Observer {
            Log.i("LiveTag2", it)
        })
        myLiveData4.addSource(myLiveData3, Observer {
            Log.i("LiveTag3", it.toString())
        })
        myLiveData4.observe(this, Observer {
            Log.i("LiveTag4", it.toString())
        })
        myLiveData.postValue("My+++++++")
        myLiveData2.postValue("You+++++++")
        myLiveData3.postValue(true)
        myLiveData4.postValue("He+++++++++++")
    }
 }
Copy the code

The results of

Although MediatorLiveData can merge multiple LiveData listens, value changes are only observed in the corresponding Observer, where a class can be defined to accept them collectively or to handle them separately

Disadvantages of LiveDate??

Call Livedata.postValue () multiple times in a short time

Let’s start with an interesting phenomenon

class LiveDataActivity : AppCompatActivity() { private val myHandler = Handler() private val myLiveData = MutableLiveData<String>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_live_data) testStick() } private fun testStick(){ myLiveData.observe(this){ Log.i("LiveTag1", Mylivedata.postvalue ("AAAAAAAAAAAAAAAAAA") myLiveData.postValue("BBBBBBBBBB") Myhandler. postDelayed(Runnable {// delay 1ms myLivedata.postValue ("CCCCCCCCCC")},1)}}Copy the code

The results ofThere is no AAA value sent out, but the CCC received it after 1ms, and LiveData kept the latest set value delivered for a short time, which is an advantage haha

LiveData changes many times in a short period of time, and the observer will only receive the latest data changes before the values are distributed

Let’s take a look at the distribution of LiveData values

Static final Object NOT_SET = new Object(); private volatile Object mData = NOT_SET; volatile Object mPendingData = NOT_SET; protected void postValue(T value) { boolean postTask; <1> synchronized (mDataLock) { postTask = mPendingData == NOT_SET; mPendingData = value; } <2> if (! postTask) { return; } ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable); }Copy the code
  1. This part of the locking code ensures that mPendingData is the latest value
  2. Ensure that mPostValueRunnable does not run again while it is running.
# Private final Runnable mPostValueRunnable = new Runnable() {@override public void run() {Object newValue; synchronized (mDataLock) { newValue = mPendingData; mPendingData = NOT_SET; } //newValue is the latest value setValue((T) newValue); }}; @MainThread protected void setValue(T value) { mData = value; dispatchingValue(null); } // Use the While value void dispatchingValue(@nullable ObserverWrapper initiator) {mDispatchingValue = true; do { mDispatchInvalidated = false; if (initiator ! = null) {close-notice (notice); initiator = null; } } } while (mDispatchInvalidated); mDispatchingValue = false; } private void considerNotify(ObserverWrapper Observer) {if (! observer.mActive) { return; } if (! observer.shouldBeActive()) { observer.activeStateChanged(false); return; } if (observer.mLastVersion >= mVersion) { return; } observer.mLastVersion = mVersion; observer.mObserver.onChanged((T) mData); }Copy the code

I drew a picture based on my personal understanding

When the actual distribution value is known in the code, it is the latest value, and the original value has been overwritten

LiveData stickiness events

When using LiveData to distribute data, no Context objects are coupled, so this mechanism avoids NPE,OOM, etc problems from the framework layer. But at the same time, we also found some problems, such as LiveData can not cancel sticky events. If a previously sent event is received by a later registered observer, it could cause us trouble.

Let’s look at a phenomenon

    private fun testStick(){
        myLiveData.observe(this){
            Log.i("LiveTag1", it)
        }
        myLiveData.postValue("AAAAAAAAAAAAAAAAAA")
        myLiveData.observe(this){
            Log.i("LiveTag2", it)
        }
        myHandler.postDelayed(Runnable {
            myLiveData.postValue("CCCCCCCCCC")
        },1)
    }
Copy the code

MyLiveData’s second observer also received the AAA change

For this reason, we can see in the previous figure that the internal implementation of the registered Observer can also receive the previously sent event by comparing the version number

If multiple interfaces share a ViewModel (with a lifetime longer than all interfaces bound) and reuse the same LiveData source that was updated on the previous interface, then the next interface that just binds to view that LiveData, It will be notified that the data has changed and the latest LiveData value will be pushed immediately, which may appear unnecessary or cause errors in some scenarios.

Refer to the link < Shenluo Tianzheng, >, here also put forward a solution, the specific code to see.

Meituan technical team

There is also an open source library on Github to replace EventBus,RxBus with LiveDate

LiveDataBus

LiveEventBus

ElegantBus(Personal recommendation ☆☆☆)

The MOOC column also suggests a solution

object HiDataBus { private val eventMap = ConcurrentHashMap<String, StickyLiveData<*>>() fun <T> with(eventName: String): StickyLiveData<T> {// Subscribe and distribute messages based on event names, // Since a liveData can only send one data type // different events, Var LiveData = eventMap[eventName] if (LiveData == null) {liveData = StickyLiveData<T>(eventName) EventMap [eventName] = liveData} return liveData as StickyLiveData<T>} But we think this reflection is not elegant enough. class StickyLiveData<T>(private val eventName: String) : LiveData<T>() { internal var mStickyData: T? = null internal var mVersion = 0 fun setStickyData(stickyData: T) {mStickyData = stickyData setValue(stickyData)} fun postStickyData(stickyData: T) {mStickyData = stickyData postValue(stickyData)} Override fun setValue(value: T) { mVersion++ super.setValue(value) } override fun postValue(value: T) { mVersion++ super.postValue(value) } override fun observe(owner: LifecycleOwner, observer: Observer<in T>) { observerSticky(owner, false, observer) } fun observerSticky(owner: LifecycleOwner, sticky: Boolean, observer: Observer<in T>) {// Allow a registered Observer to specify whether to care about sticky events //sticky =true, if data has been sent before, Then the observer are influenced by the viscosity of event messages before the owner. The lifecycle. The addObserver (LifecycleEventObserver {source, the event - > destroy incident / / monitor host, Actively remove LiveData. if (event == Lifecycle.Event.ON_DESTROY) { eventMap.remove(eventName) } }) super.observe(owner, StickyObserver(this, sticky, observer)) } } class StickyObserver<T>( val stickyLiveData: StickyLiveData<T>, val sticky: Boolean, val observer: observer <in T>) : observer <T> {// The lastVersion is aligned with the version of liveData to control the distribution of sticky events. // Sticky does not equal true, can only receive messages sent after registration, Sticky = true private var lastVersion = stickyLiveData.mVersion Override fun onChanged(t: T) {if (lastVersion >= stickyLiveData.mversion) {// No updated data needs to be sent to stickyLiveData. if (sticky && stickyLiveData.mStickyData ! = null) { observer.onChanged(stickyLiveData.mStickyData) } return } lastVersion = stickyLiveData.mVersion observer.onChanged(t) } } }Copy the code