preface

As a member of Google’s Jetpack component, LiveData has been loved by programmers since its launch. It is an observable data storage class with observable and life-cycle aware characteristics. Unlike regular observable classes, LiveData has lifecycle awareness, meaning that it follows the lifecycle of other application components such as activities, fragments, or services. This awareness ensures that LiveData updates only application component observers that are in an active lifecycle state.

Advantage:

  • Ensure that the interface conforms to data state LiveData follows observer mode. LiveData notifies the Observer when the lifecycle state changes. You can integrate code to update the interface in these observers. Instead of updating the interface every time the application data changes, the observer can update the interface every time it changes.
  • An observer that does not have a memory leak will bind to Lifecycle objects and clean up after its associated Lifecycle has been destroyed.
  • If the observer’s life cycle is inactive (such as returning an Activity in the stack), it will not receive any LiveData events.
  • You no longer need to manually handle life cycle interface components and simply observe the relevant data without stopping or resuming the observation. LiveData automatically manages all of these operations because it can sense the associated lifecycle state changes as it observes.
  • Data is always up to date. If the 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.
  • Appropriate configuration changes If an Activity or Fragment is recreated due to a configuration change (such as a device rotation), it immediately receives the latest available data.
  • Sharing resources You can use the singleton pattern to extend LiveData objects to encapsulate system services so that they can be shared across applications. The LiveData object is connected to the system service once, and then any observer that needs the corresponding resource only needs to observe the LiveData object.

Step pit and principle details:

1. LiveData can only subscribe and remove observers in the main thread

LiveData does not allow subscription and removal of observers in child threads. If subscription is made in child threads, an exception will be thrown. The key code is as follows:

@MainThread public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) { assertMainThread("observe"); 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); } @MainThread public void observeForever(@NonNull Observer<? super T> observer) { assertMainThread("observeForever"); AlwaysActiveObserver wrapper = new AlwaysActiveObserver(observer); ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper); if (existing instanceof LiveData.LifecycleBoundObserver) { throw new IllegalArgumentException("Cannot add the same observer" + " with different lifecycles"); } if (existing ! = null) { return; } wrapper.activeStateChanged(true); } @MainThread public void removeObserver(@NonNull final Observer<? super T> observer) { assertMainThread("removeObserver"); ObserverWrapper removed = mObservers.remove(observer); if (removed == null) { return; } removed.detachObserver(); removed.activeStateChanged(false); } static void assertMainThread(String methodName) { if (! ArchTaskExecutor.getInstance().isMainThread()) { throw new IllegalStateException("Cannot invoke " + methodName + " on a background" + " thread"); }}Copy the code

You can see that each method calls the assertMainThread method, which throws an IllegalStateException if the current thread is not the main thread.

2. The setValue method of LiveData can only be used in the main thread

LiveData does not allow setValue methods in child threads, otherwise an exception will be thrown. The code is as follows:

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

3. LiveData is lost

Due to the feature that LiveData always keeps the latest state, LiveData only keeps the latest data in the cache. In the normal development process, data loss is often found. Write pseudo code to verify:

package com.example.jetpack; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModelProvider; import android.os.Bundle; import android.util.Log; public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; private MyViewModel viewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); viewModel = new ViewModelProvider(this).get(MyViewModel.class); viewModel.getLiveData().observe(this, New Observer<String>() {@override public void onChanged(String data) {log.d (TAG, "receive data:" + data); }}); } @Override protected void onStart() { super.onStart(); Log.d(TAG, "recovery interface "); } @Override protected void onStop() { super.onStop(); Log.d(TAG, "leave interface "); } } class MyViewModel extends ViewModel { private MutableLiveData<String> liveData = new MutableLiveData<String>(); public LiveData<String> getLiveData() { return liveData; } public void setValue(String value) { liveData.setValue(value); } public void postValue(String value) { liveData.postValue(value); }}Copy the code

  • Call when the UI is visible

    LiveData. PostValue (” a “);

    LiveData. SetValue (” b “);

    You’ll get a” B “before you get an” A.”

  • Call when the UI is not visible

    LiveData. PostValue (” a “);

    LiveData. SetValue (” b “);

    When the UI is visible, only an “A” is received because setValue is executed first and then updated by postValue

  • Call when the UI is visible

    LiveData. SetValue (” a “);

    LiveData. SetValue (” b “);

    You get “A”, “B” in that order.

  • Call when the UI is visible

    LiveData. PostValue (” a “);

    LiveData. PostValue (” b “);

    You just get a “B.”

  • Call when the UI is not visible

    LiveData. SetValue (” a “);

    LiveData. SetValue (” b “);

    When the UI is visible, it just gets a “B”

  • Call when the UI is not visible

    LiveData. PostValue (” a “);

    LiveData. PostValue (” b “);

    When the UI is visible, it just gets a “B”

  • Call when the UI is not visible

    liveData.setValue(“a);

    LiveData. PostValue (” b “);

    When the UI is visible, it just gets a “B”

  • Call when the UI is not visible

    LiveData. PostValue (” a “);

    LiveData. SetValue (” b “);

    When the UI is visible, it just gets an “A”



    Looking at these results, found a lot of data loss, we start from the source code, analysis of the causes of these phenomena.

    Next, start with the source code of LiveData postValue:

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

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

PostValue (postToMainThread, postToMainThread, postValue, postToMainThread, postValue, postToMainThread, postValue, postToMainThread, postValue); Because the mPostValueRunnable of the first data is incomplete, postTask is false, and postValue will only perform the assignment of mPendingData = value. When the mPostValueRunnable run method is executed, it takes the latest mPendingData and calls setValue to distribute the data. Therefore, only the last data is sent, causing the previous data to be lost. Following up on the setValue method:

@MainThread protected void setValue(T value) { assertMainThread("setValue"); mVersion++; mData = value; dispatchingValue(null); } 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; } 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 (! observer.shouldBeActive()) { observer.activeStateChanged(false); return; } if (observer.mLastVersion >= mVersion) { return; } observer.mLastVersion = mVersion; observer.mObserver.onChanged((T) mData); }Copy the code

Notice that the sent value is stored by mData globally, and while the Observer is considered inactive, data is not distributed; otherwise, the onChanged method is called to call back mData. This avoids memory leaks. Continuing with the source code, let’s look at the source of the mActive active state, since it’s tied to the lifecycle, so let’s look at the code for the Activity:

public class ComponentActivity extends Activity implements LifecycleOwner, KeyEventDispatcher.Com ponent {/ * * * This is only 2 for apps that have not switched to Fragments could 1.1.0, where this * behavior is provided by <code>androidx.activity.ComponentActivity</code>. */ private LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this); @SuppressLint("RestrictedApi") @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); ReportFragment.injectIfNeededIn(this); } @NonNull @Override public Lifecycle getLifecycle() { return mLifecycleRegistry; }}Copy the code

Inject a ReportFragment into the Activity. Then look at the ReportFragment code:

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ReportFragment extends Fragment { private static final String REPORT_FRAGMENT_TAG = "androidx.lifecycle" + ".LifecycleDispatcher.report_fragment_tag"; public static void injectIfNeededIn(Activity activity) { if (Build.VERSION.SDK_INT >= 29) { // On API 29+, we can register for the correct Lifecycle callbacks directly activity.registerActivityLifecycleCallbacks( new LifecycleCallbacks()); } // Prior to API 29 and to maintain compatibility with older versions of // ProcessLifecycleOwner (which may not be updated when lifecycle-runtime is updated and // need to support activities that don't extend from FragmentActivity from  support lib), // use a framework fragment to get the correct timing of Lifecycle events android.app.FragmentManager manager = activity.getFragmentManager(); if (manager.findFragmentByTag(REPORT_FRAGMENT_TAG) == null) { manager.beginTransaction().add(new ReportFragment(), REPORT_FRAGMENT_TAG).commit(); // Hopefully, we are the first to make a transaction. manager.executePendingTransactions(); } } @SuppressWarnings("deprecation") static void dispatch(@NonNull Activity activity, @NonNull Lifecycle.Event event) { if (activity instanceof LifecycleRegistryOwner) { ((LifecycleRegistryOwner) activity).getLifecycle().handleLifecycleEvent(event); return; } if (activity instanceof LifecycleOwner) { Lifecycle lifecycle = ((LifecycleOwner) activity).getLifecycle(); if (lifecycle instanceof LifecycleRegistry) { ((LifecycleRegistry) lifecycle).handleLifecycleEvent(event); } } } static ReportFragment get(Activity activity) { return (ReportFragment) activity.getFragmentManager().findFragmentByTag( REPORT_FRAGMENT_TAG); } private ActivityInitializationListener mProcessListener; private void dispatchCreate(ActivityInitializationListener listener) { if (listener ! = null) { listener.onCreate(); } } private void dispatchStart(ActivityInitializationListener listener) { if (listener ! = null) { listener.onStart(); } } private void dispatchResume(ActivityInitializationListener listener) { if (listener ! = null) { listener.onResume(); } } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); dispatchCreate(mProcessListener); dispatch(Lifecycle.Event.ON_CREATE); } @Override public void onStart() { super.onStart(); dispatchStart(mProcessListener); dispatch(Lifecycle.Event.ON_START); } @Override public void onResume() { super.onResume(); dispatchResume(mProcessListener); dispatch(Lifecycle.Event.ON_RESUME); } @Override public void onPause() { super.onPause(); dispatch(Lifecycle.Event.ON_PAUSE); } @Override public void onStop() { super.onStop(); dispatch(Lifecycle.Event.ON_STOP); } @Override public void onDestroy() { super.onDestroy(); dispatch(Lifecycle.Event.ON_DESTROY); // just want to be sure that we won't leak reference to an activity mProcessListener = null; } private void dispatch(@NonNull Lifecycle.Event event) { if (Build.VERSION.SDK_INT < 29) { // Only dispatch events from  ReportFragment on API levels prior // to API 29\. On API 29+, this is handled by the ActivityLifecycleCallbacks // added in ReportFragment.injectIfNeededIn dispatch(getActivity(), event); } } void setProcessListener(ActivityInitializationListener processListener) { mProcessListener = processListener; } interface ActivityInitializationListener { void onCreate(); void onStart(); void onResume(); } // this class isn't inlined only because we need to add a proguard rule for it. (b/142778206) static class LifecycleCallbacks implements Application.ActivityLifecycleCallbacks { @Override public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle bundle) { } @Override public void onActivityPostCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) { dispatch(activity, Lifecycle.Event.ON_CREATE); } @Override public void onActivityStarted(@NonNull Activity activity) { } @Override public void onActivityPostStarted(@NonNull Activity activity) { dispatch(activity, Lifecycle.Event.ON_START); } @Override public void onActivityResumed(@NonNull Activity activity) { } @Override public void onActivityPostResumed(@NonNull Activity activity) { dispatch(activity, Lifecycle.Event.ON_RESUME); } @Override public void onActivityPrePaused(@NonNull Activity activity) { dispatch(activity, Lifecycle.Event.ON_PAUSE); } @Override public void onActivityPaused(@NonNull Activity activity) { } @Override public void onActivityPreStopped(@NonNull Activity activity) { dispatch(activity, Lifecycle.Event.ON_STOP); } @Override public void onActivityStopped(@NonNull Activity activity) { } @Override public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle bundle) { } @Override public void onActivityPreDestroyed(@NonNull Activity activity) { dispatch(activity, Lifecycle.Event.ON_DESTROY); } @Override public void onActivityDestroyed(@NonNull Activity activity) { } } }Copy the code

If the Android SDK version is >= 29 and the Fragment life cycle is less than 29, then the Fragment life cycle event is distributed. And handle event distribution via LifecycleRegistry, then look at the LifecycleRegistry class:

public void handleLifecycleEvent(@NonNull Lifecycle.Event event) { State next = getStateAfter(event); moveToState(next); } static State getStateAfter(Event event) { switch (event) { case ON_CREATE: case ON_STOP: return CREATED; case ON_START: case ON_PAUSE: return STARTED; case ON_RESUME: return RESUMED; case ON_DESTROY: return DESTROYED; case ON_ANY: break; } throw new IllegalArgumentException("Unexpected event value " + event); } @NonNull @Override public State getCurrentState() { return mState; } private void moveToState(State next) { if (mState == next) { return; } mState = next; if (mHandlingEvent || mAddingObserverCounter ! = 0) { mNewEventOccurred = true; // we will figure out what to do on upper level. return; } mHandlingEvent = true; sync(); mHandlingEvent = false; } private void sync() { LifecycleOwner lifecycleOwner = mLifecycleOwner.get(); . if (mState.compareTo(mObserverMap.eldest().getValue().mState) < 0) { backwardPass(lifecycleOwner); }... forwardPass(lifecycleOwner); . } mNewEventOccurred = false; } private void forwardPass(LifecycleOwner lifecycleOwner) { Iterator<Entry<LifecycleObserver, ObserverWithState>> ascendingIterator = mObserverMap.iteratorWithAdditions(); while (ascendingIterator.hasNext() && ! mNewEventOccurred) { Entry<LifecycleObserver, ObserverWithState> entry = ascendingIterator.next(); ObserverWithState observer = entry.getValue(); while ((observer.mState.compareTo(mState) < 0 && ! mNewEventOccurred && mObserverMap.contains(entry.getKey()))) { pushParentState(observer.mState); observer.dispatchEvent(lifecycleOwner, upEvent(observer.mState)); popParentState(); } } } private void backwardPass(LifecycleOwner lifecycleOwner) { Iterator<Entry<LifecycleObserver, ObserverWithState>> descendingIterator = mObserverMap.descendingIterator(); while (descendingIterator.hasNext() && ! mNewEventOccurred) { Entry<LifecycleObserver, ObserverWithState> entry = descendingIterator.next(); ObserverWithState observer = entry.getValue(); / / traverse the observer collection while ((observer.mState.com pareTo (mState) > 0 &&! mNewEventOccurred && mObserverMap.contains(entry.getKey()))) { Event event = downEvent(observer.mState); pushParentState(getStateAfter(event)); // dispatchEvent(lifecycleOwner, event); popParentState(); } } } static class ObserverWithState { State mState; LifecycleEventObserver mLifecycleObserver; ObserverWithState(LifecycleObserver observer, State initialState) { mLifecycleObserver = Lifecycling.lifecycleEventObserver(observer); mState = initialState; } void dispatchEvent(LifecycleOwner owner, Event event) { State newState = getStateAfter(event); mState = min(mState, newState); mLifecycleObserver.onStateChanged(owner, event); mState = newState; }}Copy the code

The code finally gets the ObserverWithState Observer wrapper class by iterating through the mObserverMap collection, calling dispatchEvent for event distribution, and then tracing the data source of the mObserverMap. Since it is a collection of observers, That’s where you register the observer, which is via LiveData.

@MainThread public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) { assertMainThread("observe"); 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); }Copy the code

Looking at the last line own.getlifecycle ().addobServer (Wrapper), the Activity implements the LifecycleOwner interface, back to the code for the Activity:

    final LifecycleRegistry mFragmentLifecycleRegistry = new LifecycleRegistry(this);
    @NonNull
    @Override
    public Lifecycle getLifecycle() {
        return mLifecycleRegistry;
    }
Copy the code

LifecycleRegistry’s addObserver method is finally called:

@Override public void addObserver(@NonNull LifecycleObserver observer) { ... State initialState = mState == DESTROYED ? DESTROYED : INITIALIZED; ObserverWithState statefulObserver = new ObserverWithState(observer, initialState); ObserverWithState previous = mObserverMap.putIfAbsent(observer, statefulObserver); . }Copy the code

The Observer object is wrapped with ObserverWithState and stored in the mObserverMap collection, corresponding to the above, while the addObserver method passes in the LifecycleBoundObserver object, Continue looking at the LifecycleBoundObserver 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()); } } private abstract class ObserverWrapper { final Observer<? super T> mObserver; boolean mActive; int mLastVersion = START_VERSION; ObserverWrapper(Observer<? super T> observer) { mObserver = observer; } void activeStateChanged(boolean newActive) { if (newActive == mActive) { return; } // immediately set active state, so we'd never dispatch anything to inactive // owner mActive = newActive; boolean wasInactive = LiveData.this.mActiveCount == 0; LiveData.this.mActiveCount += mActive ? 1:1; if (wasInactive && mActive) { onActive(); } if (LiveData.this.mActiveCount == 0 && ! mActive) { onInactive(); } if (mActive) { dispatchingValue(this); }}}Copy the code

See here, everything is connected, in the front LifecycleRegistry, last call mLifecycleObserver. OnStateChanged (the owner, the event), And finally call activeStateChanged to ObserverWrapper, calculate the result with shouldBeActive and assign to mActive, ShouldBeActive calls the sAtLeast method of State, which is an enumerated class:

public enum State { /** * Destroyed state for a LifecycleOwner. After this event, this Lifecycle will not dispatch * any more events. For instance, for an {@link android.app.Activity}, this state is reached * <b>right before</b> Activity's {@link android.app.Activity#onDestroy() onDestroy} call. */ DESTROYED, /** * Initialized state for a LifecycleOwner. For an {@link android.app.Activity}, this is * the state when it is constructed but has not received * {@link android.app.Activity#onCreate(android.os.Bundle) onCreate} yet. */ INITIALIZED, /** * Created state for a LifecycleOwner. For an {@link android.app.Activity}, this state * is reached in two cases: * <ul> * <li>after {@link android.app.Activity#onCreate(android.os.Bundle) onCreate} call; * <li><b>right before</b> {@link android.app.Activity#onStop() onStop} call. * </ul> */ CREATED, /** * Started state for a LifecycleOwner. For an {@link android.app.Activity}, this state * is reached in two cases: * <ul> * <li>after {@link android.app.Activity#onStart() onStart} call; * <li><b>right before</b> {@link android.app.Activity#onPause() onPause} call. * </ul> */ STARTED, /** * Resumed state for a LifecycleOwner. For an {@link android.app.Activity}, this state * is reached after {@link android.app.Activity#onResume() onResume} is called. */ RESUMED; /** * Compares if this State is greater or equal to the given {@code state}. * * @param state State to compare with * @return true if this State is greater or equal to the given {@code state} */ public boolean isAtLeast(@NonNull State state) { return compareTo(state) >= 0; }}Copy the code

In the getStateAfter method of the previous LifecycleRegistry, convert the Activity’s Lifecycle Event tirely. Event to a State object. IsAtLeast returns true only when the Activity is in the onPause, onStart, and onResume states, that is, mActive = true, or mActive = false. At this point, reviewing the previous code, LiveData will only send data when the Activity is in the onPause, onStart, and onResume states, and will only save data to the global variable mData during the rest of its life cycle. The observer is removed in the onDestroy state to avoid memory leaks. And within the activeStateChanged method of ObserverWrapper, the dispatchingValue method is called when mActive = true, sending the latest data when the UI restores visibility.

4. LiveData observeForever and removeObserver methods should be used together

In some cases, if we want to receive data even when the page is not visible, we will subscribe to the observed object with observeForever. In this case, the observer object will not automatically remove the reference, causing a memory leak and requiring us to call the removeObserver method to remove the reference at the corresponding lifecycle.

5. The same LiveData is called back multiple times in the Fragment

This is because when using fragments, there may be multiple subscriptions of LiveData. When there is data in LiveData, the data will be sent once after re-subscription, and then sometimes we only need to receive a data once. To address this problem, Google has implemented a clone class SingleLiveEvent on Stack Overflow. The mechanism is to record a setValue with an atomic AtomicBoolean. Set AtomicBoolean to false after sending once to prevent subsequent foreground refiring of data.

import androidx.annotation.MainThread; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.Observer; import java.util.concurrent.atomic.AtomicBoolean; public class SingleLiveEvent<T> extends MutableLiveData<T> { private final AtomicBoolean mPending = new AtomicBoolean(false); @Override public void observe(@NonNull LifecycleOwner owner, @NonNull final Observer<? super T> observer) { super.observe(owner, new Observer<T>() { @Override public void onChanged(@Nullable T t) { if (mPending.compareAndSet(true, false)) { observer.onChanged(t); }}}); } @MainThread public void setValue(@Nullable T t) { mPending.set(true); super.setValue(t); } /** * Used for cases where T is Void, to make calls cleaner. */ @MainThread public void call() { setValue(null); }}Copy the code