preface
With Lifecycle and ViewModel out of the way, it’s time for the more complex Part of LiveData.
When I first learned about LiveData, I refused, because you can’t come up and let me use it. First, I’ll try it. I’m not willing to use it. Both duang and duang are bugs….
A little bit into the pit of JetPack: ViewModel
A bit into the pit of JetPack: Lifecycle
A little bit into the pit of JetPack: LiveData
A little bit into the pit JetPack: NetworkBoundResource for actual foreplay
A little bit into the pit JetPack (final chapter) : Actual COMBAT MVVM
After using it, I feel my life has reached a climax.
The body of the
Of course, I do not want to listen to the blind BB, you can directly official documents. For fun, learn about new technology. Then welcome to red… A male guest, please come in!
A profile,
LiveData is an observable data holder class. Unlike regular observables, LiveData is lifecycle aware.
From the official documentation we can see two key words: observable and lifecycle awareness. In short, Google gives us a class that can be observed and has lifecycle awareness. What’s the use of that?
Go directly to demo:
Second, an introduction to
1.1. Primary official Demo
class NameViewModel : ViewModel() {
// Here new is a MutableLiveData, which is an implementation class of LiveData. LiveData is abstract and obviously cannot be new
val currentName: LiveData<String> by lazy {
MutableLiveData<String>()
}
}
class NameActivity : AppCompatActivity() {
private lateinit var mModel: NameViewModel
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
mModel = ViewModelProviders.of(this).get(NameViewModel::class.java)
// This is LifecycleOwner
mModel.currentName.observe(this, Observer { newName ->
// mNameTextView A TextViewMnametextview.text = newName})// Update the observed data, LiveData will notify the observer
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} Normally, if we want the external POST to return the implementation class (for example, with a MutableLiveData inside), that's fine. Thanks in the comments section only)
mModel.currentName.postValue("MDove")}}Copy the code
If you see a few lines of code and it suddenly becomes clear, you can skip to the next section. If you feel confused, don’t worry, we don’t have to take medicine or injections, sit down is to talk with you…
1.2 Demo Explanation
We started with a ViewModel with a NameViewModel, which is covered in the ViewModel section. There is an internal member variable called MutableLiveData. Basically, it’s a String of type LiveData. We use it with the help of the features of LiveData, but it’s still a String.
That is, if we were to use a List here, it would be MutableLiveData
>().
The Activity, we obtain the ViewModel first and then mModel. CurrentName. Observe (… ,…). Here we are looking at LiveData. We just need to handle our own UI actions in the callback. That’s mnametextView.text = newName in demo.
LiveData will be used in every postValue(…) Or value =… Observe () is called back, even if it is null.
Pay attention to
There are two points to note:
- 1. LiveData is lifecycle aware and is not active in the current LifecycleOwner (e.g
onPasue()
,onStop()
), LiveData does not call backobserve()
Because it doesn’t make sense. - 2, if LiveData is not
observe()
Then you call the postValue(…) of this LiveData /value=… , it isIt doesn’t make any difference. We can see this in the source code.
1.3 Different LiveData Implementation classes (system Implementation)
MutableLiveData:
We’ve already seen above, nothing special, just the LiveData implementation class. That’s the same thing as List and ArrayList.
MediatorLiveData:
A subclass of MutableLiveData, which is a powerful LiveData, and our map() and switchMap() implementations are based on it. The biggest feature is the ability to listen to multiple LiveData simultaneously.
Third, the advanced
The official website of this small broken demo, is really too shabby, you add some special effects ah? In this elementary usage, who can feel good! So, if you have any feelings for LiveData, let’s fight it out until morning.
3.1, the map ()
If you’re a new user of RxJava, you’re probably just as confused as I am by the “pose” operators: map, flatMap… This is also done in LiveData.
It’s a common scenario where we query an entity class with a unique ID and display data for both. The very simple business logic, shown in LiveData, looks like this:
3.1.1, use,
class NameViewModel : ViewModel() {
val userIdLiveData = MutableLiveData<Long> ()// Pseudo-code: Map in userLiveData is called when userIdLiveData changes, so we can get the id of the crime
val userLiveData: LiveData<User> = Transformations.map(userIdLiveData) { id->
// Return an instance of User
user
}
}
/ / the Activity
mModel.userLiveData.observe(this, Observer { user ->
// Notify mNameTextView to update UI when user changesMnametextview.text = user.name})// Set userIdLiveData id to 1
mModel.userIdLiveData.postValue("1")
Copy the code
For this business scenario, we only need to listen to the LiveData (userLiveData) that our users notify of UI changes, and then drive the data changes through useridLiveData.postValue (“1”).
This may not be the same as our traditional MVP thinking, after all, MVVM and MVP are different, and MVVM in this way is called data driven.
3.1.2 Map () source code
Let’s go straight to doubling.map().
@MainThread
public static <X, Y> LiveData<Y> map(
@NonNull LiveData<X> source,
@NonNull final Function<X, Y> mapFunction) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(source, new Observer<X>() {
@Override
public void onChanged(@NullableX x) { result.setValue(mapFunction.apply(x)); }});return result;
}
Copy the code
It is simple to use MediatorLiveData, and then through a higher-order function, the content returned by the higher-order function, set to LiveData, complete the map().
Now that we’ve mentioned MediatorLiveData and its addSource () method, let’s take a look at its source code.
3.1.3 MediatorLiveData source code
This part is not interesting, you can skip to 3.1.4, map() source summary…
Going into MediatorLiveData, we find that there is less code. Here take out two more important pieces of content, let’s feel together:
@MainThread
public <S> void addSource(@NonNull LiveData<S> source, @NonNull Observer<? superS> onChanged) { Source<S> e = new Source<>(source, onChanged); Source<? > existing = mSources.putIfAbsent(source, e);if(existing ! =null&& existing.mObserver ! = onChanged) {throw new IllegalArgumentException(
"This source was already added with the different observer");
}
if(existing ! =null) {
return;
}
if(hasActiveObservers()) { e.plug(); }}Copy the code
From this code, we can get a rough idea. Here we wrap our LiveData and Observer into a Source object, and this object cannot be added repeatedly.
In addition, the Source’s plug() method is called. Let’s take a look at the implementation of the inner Source class:
private static class Source<V> implements Observer<V> {
final LiveData<V> mLiveData;
final Observer<? super V> mObserver;
int mVersion = START_VERSION;
Source(LiveData<V> liveData, final Observer<? super V> observer) {
mLiveData = liveData;
mObserver = observer;
}
void plug() {
mLiveData.observeForever(this);
}
void unplug() {
mLiveData.removeObserver(this);
}
@Override
public void onChanged(@Nullable V v) {
if(mVersion ! = mLiveData.getVersion()) { mVersion = mLiveData.getVersion(); mObserver.onChanged(v); }}}Copy the code
First, the Source is an Observer. As you can see, the Observer we use externally is added to the incoming LiveData in the form of a member variable of the Source. It is worth noting that mliveData.observeForever (this) is used; .
As you can see from the use of observeForever (), we are not passing LifecycleOwner, so it is not life aware. This means that the given observer will receive all events and will never be automatically removed.
3.1.4 Summary of map() source code
Stop it, stop it. There’s no need to keep watching. Map () is based on MediatorLiveData. MediatorLiveData internally encapsulates the incoming LiveData and observers into inner classes and places them in an internally maintained map. And it automatically completes observeForever() and removeObserver() for us.
3.2, switchMap ()
3.2.1, use,
The scenario of switchMap() can be applied to switching LiveData. How do you explain that?
Common business scenarios: your business uses map(), map() uses your own network, and LiveData works fine, smoking and drinking, nothing happens… For example, the map() code above:
val userLiveData: LiveData<User> = Transformations.map(userIdLiveData) { id->
// Your own piece of logic
user
}
/ / the Activity
mViewModel.userLiveData.observe(this,Observer{->user
/ / update the UI
})
Copy the code
All of a sudden, there was no change in the data structure, no change in the UI, only change in the logic. At this point, one of your colleagues has written a method and asks you to replace it. But suddenly when you call it, this method returns a LiveData object!
Of course at this point we can ask UI to re-observe () the LiveData object:
val otherLiveData:LiveData<User> = // Colleague logic
// Re-observe () in the Activity
mViewModel.otherLiveData.observe(this,Observer{->user
/ / update the UI
})
Copy the code
But in this way, before their own writing things are not in vain? So at this point, we can use switchMap(), and we need only a few changes to set up this requirement change:
val userLiveData: LiveData<User> = Transformations.switchMap(userIdLiveData) { id->
// Just put your colleague's code here
}
Copy the code
3.2.2 switchMap() source code
With the map() source code base above, it’s easy to see what switchMap() looks like:
@MainThread
public static <X, Y> LiveData<Y> switchMap(
@NonNull LiveData<X> source,
@NonNull final Function<X, LiveData<Y>> switchMapFunction) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(source, new Observer<X>() {
LiveData<Y> mSource;
@Override
public void onChanged(@Nullable X x) {
// Get the LiveData returned from the Function, which is our new LiveData (the LiveData written by our colleagues in the background)
LiveData<Y> newLiveData = switchMapFunction.apply(x);
if (mSource == newLiveData) {
return;
}
// Remove the old LiveData
if(mSource ! =null) {
result.removeSource(mSource);
}
mSource = newLiveData;
if(mSource ! =null) {
// Add new LiveData
result.addSource(mSource, new Observer<Y>() {
@Override
public void onChanged(@Nullable Y y) {
// Notify LiveData of changesresult.setValue(y); }}); }}});return result;
}
Copy the code
Let’s compare the parameter types of switchMap() and map() :
- Function<X, LiveData> switchMapFunction
- Function<X, Y> mapFunction
Obviously one is to return the LiveData type, and the other is to change the type. So that’s the difference between these two approaches.
The code explanation can be seen in the comments, very straightforward ideas.
3.3 Use of MediatorLiveData
Above we have seen the use of map() and switchMap(). You’ve all noticed the MediatorLiveData class.
We’ve been working with one LiveData, but it’s easy to encounter multiple state changes to our requirements. Like the official demo:
LiveData liveData1 = ... ; LiveData liveData2 = ... ; MediatorLiveData liveDataMerger = new MediatorLiveData<>(); liveDataMerger.addSource(liveData1, value -> liveDataMerger.setValue(value)); liveDataMerger.addSource(liveData2, value -> liveDataMerger.setValue(value));Copy the code
As shown in the demo, we can add multiple LiveData at the same time and process our different logic according to the changes of different LiveData. Finally, call back to our UI via MediatorLiveData.
Four, source code analysis
Registration of the Observer
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
// If the current life cycle is DESTROYED, return it directly
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
return;
}
// The wrapper class did one thing in DESTROYED, removing the Observer
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
// Add to an existing Observer and throw an exception immediately after the Attach has been added
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;
}
// Add Wrapper to LifecycleOwner
owner.getLifecycle().addObserver(wrapper);
}
Copy the code
How the Observer is responded to:
public interface Observer<T> {
/**
* Called when the data is changed.
* @param t The new data
*/
void onChanged(T t);
}
Copy the code
The starting point for the trigger, obviously, is when we’re in set/postValue:
@MainThread
protected void setValue(T value) {
// Remember this value, which is used to indicate whether the data has changed
mVersion++;
mData = value;
dispatchingValue(null);
}
void dispatchingValue(@Nullable ObserverWrapper initiator) {
// Omit part of the code
for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
/ / to go in
considerNotify(iterator.next().getValue());
if (mDispatchInvalidated) {
break; }}// Omit part of the code
}
private void considerNotify(ObserverWrapper observer) {
// If the observer is not active, return. If the observer is not in the foreground, the callback will not be accepted.
if(! observer.mActive) {return;
}
// Omit part of the code
// This is a very straightforward version comparison
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
/ / callback
observer.mObserver.onChanged((T) mData);
}
Copy the code
Where is observer.mactive assigned? Lots of places. In addition to some boundary condition assignments, a more formal assignment is the void activeStateChanged(Boolean newActive) method in ObserverWrapper:
void activeStateChanged(boolean newActive) {
if (newActive == mActive) {
return;
}
mActive = newActive;
}
// This method will eventually be tuned to this method
@Override
boolean shouldBeActive() {
return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
}
public boolean isAtLeast(@NonNull State state) {
return compareTo(state) >= 0;
}
Copy the code
MActive will be assigned to each Lifecycle callback. MActive will only be true if Lifecycle is active. So our LiveData will only be called back if our Activity is in the foreground.
The end of the
To this part of the LiveData is finished, do not know if you see whether the use of LiveData feel it? If not, it is better to write it yourself and feel the cheerfulness from LiveData with your body