At noon last Friday, WE had a friendly technical exchange (mutual hatred) in the group. The reason was that liveData was belittled by liveData in the group. I couldn’t read it and replied a few words. I was afraid to say that I had read the source code of liveData before, but when I really wanted to say it, I found that a lot of things had been forgotten (I was so angry that I had a bad lunch), so I wanted to find a place to record what I had learned, which could also deepen my impression.

usage

Before talking about the existing SINS of liveData, let’s take a look at the usage of liveData. We usually use liveData together with viewModel, but when we look at the usage of liveData in our daily work or in the group, we often find some usage that I personally think is not very appropriate. Let’s take a look at these uses.

At the Repository layer we use a delay function to simulate data retrieval

Create a function in the viewModel layer to call the repository layer’s getData() function and assign the result to liveData

Then call getData() on the viewModel in the Activity layer and observe the userName.

I’m sure many of you have probably used this at some point, so let’s see what the problem is.

First of all, the viewModel is used to save data for an activity that needs to be saved when the configuration changes, or when the background system accidentally kills it. It’s a bit like savedInstanceState for an activity.

If we never configuration in the android: configchanges, when mobile phone configuration transformation, we rerun the onCreate activity is destroyed, execution to mViewModel. The userName. Observe, because liveData viscosity, It then calls mviewModel.getData () to request the network, and the result is called back. Even if the result is consistent, the onChanged method of the Observer will be called back again because of data jitter in liveData. If the onChanged method is used to assign a recyclerView item and an image is loaded, it will cause flashing problems.

As you can see, the above usage not only causes us to load multiple networks once, but also may cause data flicker, which is very bad for the user experience.

So what’s the correct one

We can request data in the Init of the ViewModel, which ensures that when the activity’s configuration changes, we do not request network data again and there is no data jitter.

Or we can use liveDataScope to initialize the userName property directly.

It is also possible to determine whether data exists before obtaining it

If the project repository layer returns a flow, you can also use the flow extension function asLiveData to convert liveData to the userName property. You can also not call the conversion function in the activity itself.

How you choose to use it is based on the project architecture.

The usage of liveData has also been briefly discussed, so let’s look at the SINS of liveData.

LiveData sin 1: stickiness, data flooding

First of all, the data stored in the viewModel can be simply divided into state data and event data. What is state data? It is what needs to be displayed on the interface. So what is event data? The so-called event data is similar to the saved data which is used to send an event, such as network failure errorMsg, which is used to pop up A toast or snackbar, or the event which is changed by the data sent by page A when we use ShareViewModel to communicate between pages. This changed data can also be considered event data.

We need to understand that liveData is designed to recover interface state data, but what if we simply use liveData to store interface event data

As a practical example, many students will store the status of network request error in liveData in viewModel, and then go to toast or snackbar in Activity Observe state liveData.

In the viewModel:

In the activity:

And then what happens? The first time we enter the activity, perhaps due toa poor network, the data load fails and we pop up a toast. However, when the activity is accidentally killed by the system due to a configuration change, re-onCreate and re-observe, since the errorData in the viewModel has a value, the value will be reversed, and the onChanged method in the Observer will be called again. Toast pops up again, this can be regarded as a design error of the program.

The cause?

Let’s first look at the LiveData.observe method

There is an important class LifecycleBoundObserver that inherits from ObserverWrapper and then implements LifecycleEventObserver. LifecycleEventObserver has the ability to observe the activity lifecycle. That’s why liveData has the ability to observe the activity lifecycle. Let’s look at the ObserverWrapper class

MObserver is the Observer that we passed in with livedata. observe. Save this for later callback to the onChanged method of the Observer. MActive means whether the activity is in the foreground, whether it is still active. If the livedata. observe method is inactive, onChanged will not be called, but if livedata. observeForever is not. The following mLastVersion is the culprit.

First, the initial value of START_VERSION is -1, and liveData also has a private property called mVersion and the initial value is also equal to START_VERSION. So every time we setValue, every time we postValue, we’re going to have mVersion++, and then we’re going to call this method

If observer.mLastVersion < mVersion, observer onChanged is called back. When I entered the activity for the first time, errorData setValue was set once, mVersion was added to 0, and then when THE system accidentally killed and reobserved, Observe.mlastversion < mVersion observer.mlastVersion < mVersion observer.mlastVersion < mVersion.

But here’s the thing. I don’t have setValue, postValue, but let’s see what’s the point if I don’t execute this notice.

But remember lifecycleBoundObserver implements LifecycleEventObserver, so let’s look at onStateChanged

When the life cycle changes, onStateChanged is called back, activeStateChanged is executed, and notified is executed. Some of the details of this code can prevent invalid callbacks and so on, so you can take a look at the idea.

How to do?

For the case of single consumer, we can use Google’s SingleLiveEvent, but the SingleLiveEvent does not solve the case of multi-consumer subscription, because the mPending is set to false after consumption, so other consumers cannot consume. See Google Demo for details

For a multi-consumer solution, check out KunMinX’s UnPeekLiveData on Github

Some students change the version value by reflection, but I don’t really recommend it. Reflection takes time and is too intrusive.

LiveData crime 2: Data loss caused by postValue

For some high frequency data sources, such as live streaming applications, we regard the callback of users joining the live broadcast room as the data source, and when the high frequency joins the live broadcast room, it is naturally considered as the high frequency data source. When we call livedata.postValue, we may find that some data may be lost in onChanged in the Observer, which is obviously not allowed

The cause?

Let’s look at the livedata. postValue function

The postToMainThread method is used to post the runnable to the main thread via handler. Then let’s see what the runnable is \

Runnable is just a call to setValue.

Handler. post(runnable) encapsulates the runnable into a message and adds it to the main thread’s messagequeue. If you don’t have delay, you’re basically running ahead of the list, and it’s even scarier when you run into runnables like performTravalsals, which insert an asynchronous barrier and wait for it to finish executing first. So it’s a little bit of time between postToMainThread and actually executing setValue, so you might call postValue again in that time, and mPendingData gets assigned again, and of course the previous value is lost.

How to do?

In fact, the bottom line is that our liveData does not have a buffer to cache the lost value, so shall we change, to RXJava flowable, to Kotlin flow, sharedFlow are ok.

This is really the fault of liveData, but to be honest, it is very difficult in high-frequency data source development, but for the sake of robustness, when using MVVM architecture, we should return flow in the Repository layer, and should not return liveData. LiveData is used in viewModel and activity Settings. For that request, the data is displayed at the UI level basically unchanged. There’s nothing wrong with using liveData in viewModel. But for the kind of high frequency refresh similar to the number of live viewers, we’d better use flow

LiveData sin 3: Data jitter

LiveData setValue does not determine whether the value is equal to the old value, and calls Observer onChanged. However, stateFlow does determine this.

But that’s not a sin, because it’s easy to wrap a judgment around it, or use it as a sign of a data callback.

The last

Technology update quickly, also is not to say that the old technology is low, it is not good, we should according to the business to do the corresponding judgement, we how to use is the most beautiful, and the idea only after know know really how be to return a responsibility, also only read after know these defect is how to return a responsibility, what should we do in specific scenarios encountered.