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.gonPasue(),onStop()), LiveData does not call backobserve()Because it doesn’t make sense.
  • 2, if LiveData is notobserve()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