Android jetpack ---- use of livedata

LiveData introduction

LivaData is a observable data container class. Specifically, LiveData can be understood as a data container, which packages the data and makes the data become an observer. When the data changes, the observer can be notified. Unlike regular observable classes, LiveData can perceive the life cycle of activities, fragments, or services.

In short, LiveData has the following advantages

  1.     LiveData follows Observer mode. When the lifecycle state changes, LiveData will notify the Observer objects, and the interface can be updated in these Observer objects
  2.     No memory leaks will be sent
  3.     If the observer's lifecycle is inactive (such as returning an Activity in the stack), it will not receive any LivaData events. However, when the inactive state becomes active, it will immediately receive the latest data (when the background Activity returns to the foreground)
  4.     When config leads to Activity/Fragment reconstruction, there is no need to manually manage data storage and recovery.

Relationship between LiveData and ViewModel

Further distinguish between ViewModel and LiveData. ViewModel is used to store various data required by the page. It also includes some business logic. For example, we can process and obtain data in ViewModel. For the page, it doesn't care about these business logic. It only cares about what the data needs to be displayed, and hopes to be notified and updated in time when the data changes. LiveData is used to notify the page when the data in the ViewModel changes. From the name LiveData, we can also infer its characteristics and functions.

Let's take a look at how to use LiveData to wrap data in ViewModel. LiveData is an abstract class and cannot be used directly, so we usually use its direct subclass MutableLiveData.

public class TimerWithLiveDataViewModel extends ViewModel
{
    //Wrap the "second" field with MutableLiveData
    private MutableLiveData<Integer> currentSecond;

    public LiveData<Integer> getCurrentSecond()
    {
        if (currentSecond == null)
        {
            currentSecond = new MutableLiveData<>();
        }
        return currentSecond;
    }
}

After LiveData is defined, how to use it to communicate between the page and ViewModel?

public class LiveDataActivity extends AppCompatActivity {

    Button button;
    @Override
    protected void onCreate(@Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.viewmodel);

        button = findViewById(R.id.btn1);
        button.setVisibility(View.VISIBLE);



        initComp();
    }

    private void initComp(){
        final TextView textView = findViewById(R.id.text);
        //Get ViewModel through ViewModelProvider
        TimerLiveDataViewModel timerViewModel = new ViewModelProvider(this).get(TimerLiveDataViewModel.class);

        //Get LiveData in ViewModel
        final MutableLiveData<Integer> liveData = (MutableLiveData<Integer>) timerViewModel.getCurrentSecond();

        //Observe the changes of data in ViewModel through liveData.observer()
        liveData.observe(this, new Observer<Integer>() {
            @Override
            public void onChanged(Integer integer) {
                textView.setText("Update time:"+integer);
            }
        });

        timerViewModel.startTiming();

        //Reset timer
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //Update the data in ViewModel through LiveData.setValue()/LiveData.postValue()
                liveData.setValue(0);
            }
        });
    }
}


On the page, we use the LiveData.observe() method to observe the data wrapped in LiveData. Conversely, when we want to modify the data wrapped in LiveData, we can use LiveData.postValue()/LiveData.setValue(). Postvalue () is used in non UI threads. If it is in UI threads, setValue () method is used.

Let's dig into the source code of the LiveData.observe() method.

@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
    if (owner.getLifecycle().getCurrentState() == DESTROYED) {
        // ignore
        return;
    }
    LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
    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;
    }
    owner.getLifecycle().addObserver(wrapper);
}

It can be seen from the source code that the first parameter received by the Observer() method is a LifecleOwner object, and we passed in this, because the grandfather class of this implements this interface, which is the LifecleOwner object, and LiveData will have specific life cycle awareness.

First, get the status of the current page through owner.getLifecycle().getCurrentState(). If the current page is destroyed, it will be returned directly, that is, LiveData will automatically clear the association with the page.


LifecycleBoundObserver source code

 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() {
            return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
        }

        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
            if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
                removeObserver(mObserver);
                return;
            }
            activeStateChanged(shouldBeActive());
        }

When LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer) is called, the essence is to wrap the observer through the observer wrapper, so that LiveData can monitor the life cycle state through onStateChanged and shouldBeActive methods

  1.     shouldBeActive calls the LiftCycle method here to indicate that if the status of the current life cycle is OnStart, onresume and onpause, it returns true, that is, only these three statuses can receive data updates.
  2.     onStateChanged is the method of the LifecycleEventObserver interface. When the lifecycle sends a change, it will be recalled. If the current lifecycle state is destroy, the observer will be removed directly. Otherwise, activeStateChanged(shouldBeActive()) will be called; Methods activate the observer

The last line of code in the method associates the observer with the life cycle of the Activity. Therefore, LivaData can perceive the life cycle of the page.


Summary of observer method

  1.     Judge whether the current page has been destroyed. If the current page is destroyed, LiveData will automatically clear the association with the page
  2.     Wrap the observer with lifecycle bundobserver
  3.     Judge whether the current observer has been added, and return directly after adding
  4.     Associate the observer method with the lifecycle of the Activity

setValue method

 @MainThread
    protected void setValue(T value) {
        assertMainThread("setValue");
        mVersion++;
        mData = value;
        dispatchingValue(null);
    }

In setValue(), the first assertion is the main thread. The key here is the dispatchingValue(null) method

void dispatchingValue(@Nullable ObserverWrapper initiator) {
        if (mDispatchingValue) {
            mDispatchInvalidated = true;
            return;
        }
        mDispatchingValue = true;
        do {
            mDispatchInvalidated = false;
            if (initiator != null) {
                considerNotify(initiator);
                initiator = null;
            } else {
                for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                    considerNotify(iterator.next().getValue());
                    if (mDispatchInvalidated) {
                        break;
                    }
                }
            }
        } while (mDispatchInvalidated);
        mDispatchingValue = false;
    }

This method sends data to only active observers. Every time the dispathchingValue passes in null, the else part of the code will be used. At this time, all observers will be traversed. Finally, the data will be distributed to all observers by calling considerNotify()
 

private void considerNotify(ObserverWrapper observer) {
        if (!observer.mActive) {
            return;
        }
        // Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
        //
        // we still first check observer.active to keep it as the entrance for events. So even if
        // the observer moved to an active state, if we've not received that event, we better not
        // notify for a more predictable notification order.
        //If the current observer is not active, that is, the current page is destroyed, return directly
        if (!observer.shouldBeActive()) {
            observer.activeStateChanged(false);
            return;
        }
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        observer.mLastVersion = mVersion;
        observer.mObserver.onChanged((T) mData);
    }

Only when it is active and the data is up-to-date, it will distribute the data, and finally call back to the familiar onChanged() method.





postValue method

 protected void postValue(T value) {
        boolean postTask;
        synchronized (mDataLock) {
            postTask = mPendingData == NOT_SET;
            mPendingData = value;
        }
        if (!postTask) {
            return;
        }
        ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
    }

The postValue method can send data in the child thread (non UI thread), but the onChanged() method is always in the main thread? The answer is in the postToMainThread(mPostValueRunnable) method;

private final Runnable mPostValueRunnable = new Runnable() {
        @SuppressWarnings("unchecked")
        @Override
        public void run() {
            Object newValue;
            synchronized (mDataLock) {
                newValue = mPendingData;
                mPendingData = NOT_SET;
            }
            setValue((T) newValue);
        }
    };

Create a Handler to send the tasks in the child thread to the main thread for execution. Its essence is to call the setValue() method

LiveData.observeForever() method

If you want to return to the data immediately after setValue/postValue, regardless of the page's life cycle. Then you can use the observerveforever() method, which is not much different from observer(). Because AlwaysActiveObserver does not implement the GenericLifecycleObserver interface, it cannot sense the life cycle.

However, it should be noted that after use up, we must remember to call removeObserver() method in onDestroy() method to stop the observation of LiveData, otherwise LiveData will always be activated, Activity will never be automatically recovered by the system, and it will cause internal leakage.


 

Keywords: Android jetpack LiveData

Added by jakep on Sun, 26 Sep 2021 20:27:30 +0300