Create lifecycle:livedata-core dependency
Move the core classes of LiveData into a separate
lifecycle:livedata-core dependency that represents
the very minimal API surface needed for LiveData.
The lifecycle:livedata dependency will keep the
non-essential LiveData APIs (such as Transformations
and MediatorLiveData) and have livedata-core as a
transitive dependency.
Test: tests still pass
BUG: 70681466
Change-Id: I1d2974f7f95f27e19bcca8be32d0ddc4283d5d43
diff --git a/lifecycle/livedata-core/src/main/AndroidManifest.xml b/lifecycle/livedata-core/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..6d2f964
--- /dev/null
+++ b/lifecycle/livedata-core/src/main/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<!--
+ ~ Copyright 2017 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.arch.lifecycle.livedata.core">
+</manifest>
diff --git a/lifecycle/livedata-core/src/main/java/android/arch/lifecycle/LiveData.java b/lifecycle/livedata-core/src/main/java/android/arch/lifecycle/LiveData.java
new file mode 100644
index 0000000..3a753a1
--- /dev/null
+++ b/lifecycle/livedata-core/src/main/java/android/arch/lifecycle/LiveData.java
@@ -0,0 +1,439 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import static android.arch.lifecycle.Lifecycle.State.DESTROYED;
+import static android.arch.lifecycle.Lifecycle.State.STARTED;
+
+import android.arch.core.executor.ArchTaskExecutor;
+import android.arch.core.internal.SafeIterableMap;
+import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * LiveData is a data holder class that can be observed within a given lifecycle.
+ * This means that an {@link Observer} can be added in a pair with a {@link LifecycleOwner}, and
+ * this observer will be notified about modifications of the wrapped data only if the paired
+ * LifecycleOwner is in active state. LifecycleOwner is considered as active, if its state is
+ * {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED}. An observer added via
+ * {@link #observeForever(Observer)} is considered as always active and thus will be always notified
+ * about modifications. For those observers, you should manually call
+ * {@link #removeObserver(Observer)}.
+ *
+ * <p> An observer added with a Lifecycle will be automatically removed if the corresponding
+ * Lifecycle moves to {@link Lifecycle.State#DESTROYED} state. This is especially useful for
+ * activities and fragments where they can safely observe LiveData and not worry about leaks:
+ * they will be instantly unsubscribed when they are destroyed.
+ *
+ * <p>
+ * In addition, LiveData has {@link LiveData#onActive()} and {@link LiveData#onInactive()} methods
+ * to get notified when number of active {@link Observer}s change between 0 and 1.
+ * This allows LiveData to release any heavy resources when it does not have any Observers that
+ * are actively observing.
+ * <p>
+ * This class is designed to hold individual data fields of {@link ViewModel},
+ * but can also be used for sharing data between different modules in your application
+ * in a decoupled fashion.
+ *
+ * @param <T> The type of data held by this instance
+ * @see ViewModel
+ */
+public abstract class LiveData<T> {
+ private final Object mDataLock = new Object();
+ static final int START_VERSION = -1;
+ private static final Object NOT_SET = new Object();
+
+ private SafeIterableMap<Observer<T>, ObserverWrapper> mObservers =
+ new SafeIterableMap<>();
+
+ // how many observers are in active state
+ private int mActiveCount = 0;
+ private volatile Object mData = NOT_SET;
+ // when setData is called, we set the pending data and actual data swap happens on the main
+ // thread
+ private volatile Object mPendingData = NOT_SET;
+ private int mVersion = START_VERSION;
+
+ private boolean mDispatchingValue;
+ @SuppressWarnings("FieldCanBeLocal")
+ private boolean mDispatchInvalidated;
+ private final Runnable mPostValueRunnable = new Runnable() {
+ @Override
+ public void run() {
+ Object newValue;
+ synchronized (mDataLock) {
+ newValue = mPendingData;
+ mPendingData = NOT_SET;
+ }
+ //noinspection unchecked
+ setValue((T) newValue);
+ }
+ };
+
+ 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;
+ //noinspection unchecked
+ observer.mObserver.onChanged((T) mData);
+ }
+
+ private 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<T>, ObserverWrapper>> iterator =
+ mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
+ considerNotify(iterator.next().getValue());
+ if (mDispatchInvalidated) {
+ break;
+ }
+ }
+ }
+ } while (mDispatchInvalidated);
+ mDispatchingValue = false;
+ }
+
+ /**
+ * Adds the given observer to the observers list within the lifespan of the given
+ * owner. The events are dispatched on the main thread. If LiveData already has data
+ * set, it will be delivered to the observer.
+ * <p>
+ * The observer will only receive events if the owner is in {@link Lifecycle.State#STARTED}
+ * or {@link Lifecycle.State#RESUMED} state (active).
+ * <p>
+ * If the owner moves to the {@link Lifecycle.State#DESTROYED} state, the observer will
+ * automatically be removed.
+ * <p>
+ * When data changes while the {@code owner} is not active, it will not receive any updates.
+ * If it becomes active again, it will receive the last available data automatically.
+ * <p>
+ * LiveData keeps a strong reference to the observer and the owner as long as the
+ * given LifecycleOwner is not destroyed. When it is destroyed, LiveData removes references to
+ * the observer & the owner.
+ * <p>
+ * If the given owner is already in {@link Lifecycle.State#DESTROYED} state, LiveData
+ * ignores the call.
+ * <p>
+ * If the given owner, observer tuple is already in the list, the call is ignored.
+ * If the observer is already in the list with another owner, LiveData throws an
+ * {@link IllegalArgumentException}.
+ *
+ * @param owner The LifecycleOwner which controls the observer
+ * @param observer The observer that will receive the events
+ */
+ @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);
+ }
+
+ /**
+ * Adds the given observer to the observers list. This call is similar to
+ * {@link LiveData#observe(LifecycleOwner, Observer)} with a LifecycleOwner, which
+ * is always active. This means that the given observer will receive all events and will never
+ * be automatically removed. You should manually call {@link #removeObserver(Observer)} to stop
+ * observing this LiveData.
+ * While LiveData has one of such observers, it will be considered
+ * as active.
+ * <p>
+ * If the observer was already added with an owner to this LiveData, LiveData throws an
+ * {@link IllegalArgumentException}.
+ *
+ * @param observer The observer that will receive the events
+ */
+ @MainThread
+ public void observeForever(@NonNull Observer<T> observer) {
+ AlwaysActiveObserver wrapper = new AlwaysActiveObserver(observer);
+ ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
+ if (existing != null && existing instanceof LiveData.LifecycleBoundObserver) {
+ throw new IllegalArgumentException("Cannot add the same observer"
+ + " with different lifecycles");
+ }
+ if (existing != null) {
+ return;
+ }
+ wrapper.activeStateChanged(true);
+ }
+
+ /**
+ * Removes the given observer from the observers list.
+ *
+ * @param observer The Observer to receive events.
+ */
+ @MainThread
+ public void removeObserver(@NonNull final Observer<T> observer) {
+ assertMainThread("removeObserver");
+ ObserverWrapper removed = mObservers.remove(observer);
+ if (removed == null) {
+ return;
+ }
+ removed.detachObserver();
+ removed.activeStateChanged(false);
+ }
+
+ /**
+ * Removes all observers that are tied to the given {@link LifecycleOwner}.
+ *
+ * @param owner The {@code LifecycleOwner} scope for the observers to be removed.
+ */
+ @SuppressWarnings("WeakerAccess")
+ @MainThread
+ public void removeObservers(@NonNull final LifecycleOwner owner) {
+ assertMainThread("removeObservers");
+ for (Map.Entry<Observer<T>, ObserverWrapper> entry : mObservers) {
+ if (entry.getValue().isAttachedTo(owner)) {
+ removeObserver(entry.getKey());
+ }
+ }
+ }
+
+ /**
+ * Posts a task to a main thread to set the given value. So if you have a following code
+ * executed in the main thread:
+ * <pre class="prettyprint">
+ * liveData.postValue("a");
+ * liveData.setValue("b");
+ * </pre>
+ * The value "b" would be set at first and later the main thread would override it with
+ * the value "a".
+ * <p>
+ * If you called this method multiple times before a main thread executed a posted task, only
+ * the last value would be dispatched.
+ *
+ * @param value The new value
+ */
+ protected void postValue(T value) {
+ boolean postTask;
+ synchronized (mDataLock) {
+ postTask = mPendingData == NOT_SET;
+ mPendingData = value;
+ }
+ if (!postTask) {
+ return;
+ }
+ ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
+ }
+
+ /**
+ * Sets the value. If there are active observers, the value will be dispatched to them.
+ * <p>
+ * This method must be called from the main thread. If you need set a value from a background
+ * thread, you can use {@link #postValue(Object)}
+ *
+ * @param value The new value
+ */
+ @MainThread
+ protected void setValue(T value) {
+ assertMainThread("setValue");
+ mVersion++;
+ mData = value;
+ dispatchingValue(null);
+ }
+
+ /**
+ * Returns the current value.
+ * Note that calling this method on a background thread does not guarantee that the latest
+ * value set will be received.
+ *
+ * @return the current value
+ */
+ @Nullable
+ public T getValue() {
+ Object data = mData;
+ if (data != NOT_SET) {
+ //noinspection unchecked
+ return (T) data;
+ }
+ return null;
+ }
+
+ int getVersion() {
+ return mVersion;
+ }
+
+ /**
+ * Called when the number of active observers change to 1 from 0.
+ * <p>
+ * This callback can be used to know that this LiveData is being used thus should be kept
+ * up to date.
+ */
+ protected void onActive() {
+
+ }
+
+ /**
+ * Called when the number of active observers change from 1 to 0.
+ * <p>
+ * This does not mean that there are no observers left, there may still be observers but their
+ * lifecycle states aren't {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED}
+ * (like an Activity in the back stack).
+ * <p>
+ * You can check if there are observers via {@link #hasObservers()}.
+ */
+ protected void onInactive() {
+
+ }
+
+ /**
+ * Returns true if this LiveData has observers.
+ *
+ * @return true if this LiveData has observers
+ */
+ @SuppressWarnings("WeakerAccess")
+ public boolean hasObservers() {
+ return mObservers.size() > 0;
+ }
+
+ /**
+ * Returns true if this LiveData has active observers.
+ *
+ * @return true if this LiveData has active observers
+ */
+ @SuppressWarnings("WeakerAccess")
+ public boolean hasActiveObservers() {
+ return mActiveCount > 0;
+ }
+
+ class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver {
+ @NonNull final LifecycleOwner mOwner;
+
+ LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<T> observer) {
+ super(observer);
+ mOwner = owner;
+ }
+
+ @Override
+ boolean shouldBeActive() {
+ return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
+ }
+
+ @Override
+ public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
+ if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
+ removeObserver(mObserver);
+ return;
+ }
+ activeStateChanged(shouldBeActive());
+ }
+
+ @Override
+ boolean isAttachedTo(LifecycleOwner owner) {
+ return mOwner == owner;
+ }
+
+ @Override
+ void detachObserver() {
+ mOwner.getLifecycle().removeObserver(this);
+ }
+ }
+
+ private abstract class ObserverWrapper {
+ final Observer<T> mObserver;
+ boolean mActive;
+ int mLastVersion = START_VERSION;
+
+ ObserverWrapper(Observer<T> observer) {
+ mObserver = observer;
+ }
+
+ abstract boolean shouldBeActive();
+
+ boolean isAttachedTo(LifecycleOwner owner) {
+ return false;
+ }
+
+ void detachObserver() {
+ }
+
+ 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);
+ }
+ }
+ }
+
+ private class AlwaysActiveObserver extends ObserverWrapper {
+
+ AlwaysActiveObserver(Observer<T> observer) {
+ super(observer);
+ }
+
+ @Override
+ boolean shouldBeActive() {
+ return true;
+ }
+ }
+
+ private static void assertMainThread(String methodName) {
+ if (!ArchTaskExecutor.getInstance().isMainThread()) {
+ throw new IllegalStateException("Cannot invoke " + methodName + " on a background"
+ + " thread");
+ }
+ }
+}
diff --git a/lifecycle/livedata-core/src/main/java/android/arch/lifecycle/MutableLiveData.java b/lifecycle/livedata-core/src/main/java/android/arch/lifecycle/MutableLiveData.java
new file mode 100644
index 0000000..ecd7752
--- /dev/null
+++ b/lifecycle/livedata-core/src/main/java/android/arch/lifecycle/MutableLiveData.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+/**
+ * {@link LiveData} which publicly exposes {@link #setValue(T)} and {@link #postValue(T)} method.
+ *
+ * @param <T> The type of data hold by this instance
+ */
+@SuppressWarnings("WeakerAccess")
+public class MutableLiveData<T> extends LiveData<T> {
+ @Override
+ public void postValue(T value) {
+ super.postValue(value);
+ }
+
+ @Override
+ public void setValue(T value) {
+ super.setValue(value);
+ }
+}
diff --git a/lifecycle/livedata-core/src/main/java/android/arch/lifecycle/Observer.java b/lifecycle/livedata-core/src/main/java/android/arch/lifecycle/Observer.java
new file mode 100644
index 0000000..0e36775
--- /dev/null
+++ b/lifecycle/livedata-core/src/main/java/android/arch/lifecycle/Observer.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import android.support.annotation.Nullable;
+
+/**
+ * A simple callback that can receive from {@link LiveData}.
+ *
+ * @param <T> The type of the parameter
+ *
+ * @see LiveData LiveData - for a usage description.
+ */
+public interface Observer<T> {
+ /**
+ * Called when the data is changed.
+ * @param t The new data
+ */
+ void onChanged(@Nullable T t);
+}
diff --git a/lifecycle/livedata-core/src/test/java/android/arch/lifecycle/LiveDataTest.java b/lifecycle/livedata-core/src/test/java/android/arch/lifecycle/LiveDataTest.java
new file mode 100644
index 0000000..046059b
--- /dev/null
+++ b/lifecycle/livedata-core/src/test/java/android/arch/lifecycle/LiveDataTest.java
@@ -0,0 +1,842 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.only;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.arch.core.executor.ArchTaskExecutor;
+import android.arch.core.executor.testing.InstantTaskExecutorRule;
+import android.support.annotation.Nullable;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
+
+@SuppressWarnings({"unchecked"})
+@RunWith(JUnit4.class)
+public class LiveDataTest {
+
+ @Rule
+ public InstantTaskExecutorRule mInstantTaskExecutorRule = new InstantTaskExecutorRule();
+
+ private PublicLiveData<String> mLiveData;
+ private MethodExec mActiveObserversChanged;
+
+ private LifecycleOwner mOwner;
+ private LifecycleRegistry mRegistry;
+
+ private LifecycleOwner mOwner2;
+ private LifecycleRegistry mRegistry2;
+
+ private LifecycleOwner mOwner3;
+ private Lifecycle mLifecycle3;
+ private Observer<String> mObserver3;
+
+ private LifecycleOwner mOwner4;
+ private Lifecycle mLifecycle4;
+ private Observer<String> mObserver4;
+
+ private boolean mInObserver;
+
+ @Before
+ public void init() {
+ mLiveData = new PublicLiveData<>();
+
+ mActiveObserversChanged = mock(MethodExec.class);
+ mLiveData.activeObserversChanged = mActiveObserversChanged;
+
+ mOwner = mock(LifecycleOwner.class);
+ mRegistry = new LifecycleRegistry(mOwner);
+ when(mOwner.getLifecycle()).thenReturn(mRegistry);
+
+ mOwner2 = mock(LifecycleOwner.class);
+ mRegistry2 = new LifecycleRegistry(mOwner2);
+ when(mOwner2.getLifecycle()).thenReturn(mRegistry2);
+
+ mInObserver = false;
+ }
+
+ @Before
+ public void initNonLifecycleRegistry() {
+ mOwner3 = mock(LifecycleOwner.class);
+ mLifecycle3 = mock(Lifecycle.class);
+ mObserver3 = (Observer<String>) mock(Observer.class);
+ when(mOwner3.getLifecycle()).thenReturn(mLifecycle3);
+
+ mOwner4 = mock(LifecycleOwner.class);
+ mLifecycle4 = mock(Lifecycle.class);
+ mObserver4 = (Observer<String>) mock(Observer.class);
+ when(mOwner4.getLifecycle()).thenReturn(mLifecycle4);
+ }
+
+ @After
+ public void removeExecutorDelegate() {
+ ArchTaskExecutor.getInstance().setDelegate(null);
+ }
+
+ @Test
+ public void testObserverToggle() {
+ Observer<String> observer = (Observer<String>) mock(Observer.class);
+ mLiveData.observe(mOwner, observer);
+
+ verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+ assertThat(mLiveData.hasObservers(), is(true));
+ assertThat(mLiveData.hasActiveObservers(), is(false));
+
+ mLiveData.removeObserver(observer);
+ verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+ assertThat(mLiveData.hasObservers(), is(false));
+ assertThat(mLiveData.hasActiveObservers(), is(false));
+ }
+
+ @Test
+ public void testActiveObserverToggle() {
+ Observer<String> observer = (Observer<String>) mock(Observer.class);
+ mLiveData.observe(mOwner, observer);
+
+ verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+ assertThat(mLiveData.hasObservers(), is(true));
+ assertThat(mLiveData.hasActiveObservers(), is(false));
+
+ mRegistry.handleLifecycleEvent(ON_START);
+ verify(mActiveObserversChanged).onCall(true);
+ assertThat(mLiveData.hasActiveObservers(), is(true));
+ reset(mActiveObserversChanged);
+
+ mRegistry.handleLifecycleEvent(ON_STOP);
+ verify(mActiveObserversChanged).onCall(false);
+ assertThat(mLiveData.hasActiveObservers(), is(false));
+ assertThat(mLiveData.hasObservers(), is(true));
+
+ reset(mActiveObserversChanged);
+ mRegistry.handleLifecycleEvent(ON_START);
+ verify(mActiveObserversChanged).onCall(true);
+ assertThat(mLiveData.hasActiveObservers(), is(true));
+ assertThat(mLiveData.hasObservers(), is(true));
+
+ reset(mActiveObserversChanged);
+ mLiveData.removeObserver(observer);
+ verify(mActiveObserversChanged).onCall(false);
+ assertThat(mLiveData.hasActiveObservers(), is(false));
+ assertThat(mLiveData.hasObservers(), is(false));
+
+ verifyNoMoreInteractions(mActiveObserversChanged);
+ }
+
+ @Test
+ public void testReAddSameObserverTuple() {
+ Observer<String> observer = (Observer<String>) mock(Observer.class);
+ mLiveData.observe(mOwner, observer);
+ mLiveData.observe(mOwner, observer);
+ assertThat(mLiveData.hasObservers(), is(true));
+ }
+
+ @Test
+ public void testAdd2ObserversWithSameOwnerAndRemove() {
+ Observer<String> o1 = (Observer<String>) mock(Observer.class);
+ Observer<String> o2 = (Observer<String>) mock(Observer.class);
+ mLiveData.observe(mOwner, o1);
+ mLiveData.observe(mOwner, o2);
+ assertThat(mLiveData.hasObservers(), is(true));
+ verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+
+ mRegistry.handleLifecycleEvent(ON_START);
+ verify(mActiveObserversChanged).onCall(true);
+ mLiveData.setValue("a");
+ verify(o1).onChanged("a");
+ verify(o2).onChanged("a");
+
+ mLiveData.removeObservers(mOwner);
+
+ assertThat(mLiveData.hasObservers(), is(false));
+ assertThat(mRegistry.getObserverCount(), is(0));
+ }
+
+ @Test
+ public void testAddSameObserverIn2LifecycleOwners() {
+ Observer<String> observer = (Observer<String>) mock(Observer.class);
+
+ mLiveData.observe(mOwner, observer);
+ Throwable throwable = null;
+ try {
+ mLiveData.observe(mOwner2, observer);
+ } catch (Throwable t) {
+ throwable = t;
+ }
+ assertThat(throwable, instanceOf(IllegalArgumentException.class));
+ //noinspection ConstantConditions
+ assertThat(throwable.getMessage(),
+ is("Cannot add the same observer with different lifecycles"));
+ }
+
+ @Test
+ public void testRemoveDestroyedObserver() {
+ Observer<String> observer = (Observer<String>) mock(Observer.class);
+ mLiveData.observe(mOwner, observer);
+ mRegistry.handleLifecycleEvent(ON_START);
+ verify(mActiveObserversChanged).onCall(true);
+ assertThat(mLiveData.hasObservers(), is(true));
+ assertThat(mLiveData.hasActiveObservers(), is(true));
+
+ reset(mActiveObserversChanged);
+
+ mRegistry.handleLifecycleEvent(ON_DESTROY);
+ assertThat(mLiveData.hasObservers(), is(false));
+ assertThat(mLiveData.hasActiveObservers(), is(false));
+ verify(mActiveObserversChanged).onCall(false);
+ }
+
+ @Test
+ public void testInactiveRegistry() {
+ Observer<String> observer = (Observer<String>) mock(Observer.class);
+ mRegistry.handleLifecycleEvent(ON_DESTROY);
+ mLiveData.observe(mOwner, observer);
+ assertThat(mLiveData.hasObservers(), is(false));
+ }
+
+ @Test
+ public void testNotifyActiveInactive() {
+ Observer<String> observer = (Observer<String>) mock(Observer.class);
+ mRegistry.handleLifecycleEvent(ON_CREATE);
+ mLiveData.observe(mOwner, observer);
+ mLiveData.setValue("a");
+ verify(observer, never()).onChanged(anyString());
+ mRegistry.handleLifecycleEvent(ON_START);
+ verify(observer).onChanged("a");
+
+ mLiveData.setValue("b");
+ verify(observer).onChanged("b");
+
+ mRegistry.handleLifecycleEvent(ON_STOP);
+ mLiveData.setValue("c");
+ verify(observer, never()).onChanged("c");
+
+ mRegistry.handleLifecycleEvent(ON_START);
+ verify(observer).onChanged("c");
+
+ reset(observer);
+ mRegistry.handleLifecycleEvent(ON_STOP);
+ mRegistry.handleLifecycleEvent(ON_START);
+ verify(observer, never()).onChanged(anyString());
+ }
+
+ @Test
+ public void testStopObservingOwner_onDestroy() {
+ Observer<String> observer = (Observer<String>) mock(Observer.class);
+ mRegistry.handleLifecycleEvent(ON_CREATE);
+ mLiveData.observe(mOwner, observer);
+ assertThat(mRegistry.getObserverCount(), is(1));
+ mRegistry.handleLifecycleEvent(ON_DESTROY);
+ assertThat(mRegistry.getObserverCount(), is(0));
+ }
+
+ @Test
+ public void testStopObservingOwner_onStopObserving() {
+ Observer<String> observer = (Observer<String>) mock(Observer.class);
+ mRegistry.handleLifecycleEvent(ON_CREATE);
+ mLiveData.observe(mOwner, observer);
+ assertThat(mRegistry.getObserverCount(), is(1));
+
+ mLiveData.removeObserver(observer);
+ assertThat(mRegistry.getObserverCount(), is(0));
+ }
+
+ @Test
+ public void testActiveChangeInCallback() {
+ mRegistry.handleLifecycleEvent(ON_START);
+ Observer<String> observer1 = spy(new Observer<String>() {
+ @Override
+ public void onChanged(@Nullable String s) {
+ mRegistry.handleLifecycleEvent(ON_STOP);
+ assertThat(mLiveData.hasObservers(), is(true));
+ assertThat(mLiveData.hasActiveObservers(), is(false));
+ }
+ });
+ final Observer observer2 = mock(Observer.class);
+ mLiveData.observe(mOwner, observer1);
+ mLiveData.observe(mOwner, observer2);
+ mLiveData.setValue("bla");
+ verify(observer1).onChanged(anyString());
+ verify(observer2, Mockito.never()).onChanged(anyString());
+ assertThat(mLiveData.hasObservers(), is(true));
+ assertThat(mLiveData.hasActiveObservers(), is(false));
+ }
+
+ @Test
+ public void testActiveChangeInCallback2() {
+ Observer<String> observer1 = spy(new Observer<String>() {
+ @Override
+ public void onChanged(@Nullable String s) {
+ assertThat(mInObserver, is(false));
+ mInObserver = true;
+ mRegistry.handleLifecycleEvent(ON_START);
+ assertThat(mLiveData.hasActiveObservers(), is(true));
+ mInObserver = false;
+ }
+ });
+ final Observer observer2 = spy(new FailReentranceObserver());
+ mLiveData.observeForever(observer1);
+ mLiveData.observe(mOwner, observer2);
+ mLiveData.setValue("bla");
+ verify(observer1).onChanged(anyString());
+ verify(observer2).onChanged(anyString());
+ assertThat(mLiveData.hasObservers(), is(true));
+ assertThat(mLiveData.hasActiveObservers(), is(true));
+ }
+
+ @Test
+ public void testObserverRemovalInCallback() {
+ mRegistry.handleLifecycleEvent(ON_START);
+ Observer<String> observer = spy(new Observer<String>() {
+ @Override
+ public void onChanged(@Nullable String s) {
+ assertThat(mLiveData.hasObservers(), is(true));
+ mLiveData.removeObserver(this);
+ assertThat(mLiveData.hasObservers(), is(false));
+ }
+ });
+ mLiveData.observe(mOwner, observer);
+ mLiveData.setValue("bla");
+ verify(observer).onChanged(anyString());
+ assertThat(mLiveData.hasObservers(), is(false));
+ }
+
+ @Test
+ public void testObserverAdditionInCallback() {
+ mRegistry.handleLifecycleEvent(ON_START);
+ final Observer observer2 = spy(new FailReentranceObserver());
+ Observer<String> observer1 = spy(new Observer<String>() {
+ @Override
+ public void onChanged(@Nullable String s) {
+ assertThat(mInObserver, is(false));
+ mInObserver = true;
+ mLiveData.observe(mOwner, observer2);
+ assertThat(mLiveData.hasObservers(), is(true));
+ assertThat(mLiveData.hasActiveObservers(), is(true));
+ mInObserver = false;
+ }
+ });
+ mLiveData.observe(mOwner, observer1);
+ mLiveData.setValue("bla");
+ verify(observer1).onChanged(anyString());
+ verify(observer2).onChanged(anyString());
+ assertThat(mLiveData.hasObservers(), is(true));
+ assertThat(mLiveData.hasActiveObservers(), is(true));
+ }
+
+ @Test
+ public void testObserverWithoutLifecycleOwner() {
+ Observer<String> observer = (Observer<String>) mock(Observer.class);
+ mLiveData.setValue("boring");
+ mLiveData.observeForever(observer);
+ verify(mActiveObserversChanged).onCall(true);
+ verify(observer).onChanged("boring");
+ mLiveData.setValue("tihs");
+ verify(observer).onChanged("tihs");
+ mLiveData.removeObserver(observer);
+ verify(mActiveObserversChanged).onCall(false);
+ mLiveData.setValue("boring");
+ reset(observer);
+ verify(observer, never()).onChanged(anyString());
+ }
+
+ @Test
+ public void testSetValueDuringSetValue() {
+ mRegistry.handleLifecycleEvent(ON_START);
+ final Observer observer1 = spy(new Observer<String>() {
+ @Override
+ public void onChanged(String o) {
+ assertThat(mInObserver, is(false));
+ mInObserver = true;
+ if (o.equals(("bla"))) {
+ mLiveData.setValue("gt");
+ }
+ mInObserver = false;
+ }
+ });
+ final Observer observer2 = spy(new FailReentranceObserver());
+ mLiveData.observe(mOwner, observer1);
+ mLiveData.observe(mOwner, observer2);
+ mLiveData.setValue("bla");
+ verify(observer1, Mockito.atMost(2)).onChanged("gt");
+ verify(observer2, Mockito.atMost(2)).onChanged("gt");
+ }
+
+ @Test
+ public void testRemoveDuringSetValue() {
+ mRegistry.handleLifecycleEvent(ON_START);
+ final Observer observer1 = spy(new Observer<String>() {
+ @Override
+ public void onChanged(String o) {
+ mLiveData.removeObserver(this);
+ }
+ });
+ Observer<String> observer2 = (Observer<String>) mock(Observer.class);
+ mLiveData.observeForever(observer1);
+ mLiveData.observe(mOwner, observer2);
+ mLiveData.setValue("gt");
+ verify(observer2).onChanged("gt");
+ }
+
+ @Test
+ public void testDataChangeDuringStateChange() {
+ mRegistry.handleLifecycleEvent(ON_START);
+ mRegistry.addObserver(new LifecycleObserver() {
+ @OnLifecycleEvent(ON_STOP)
+ public void onStop() {
+ // change data in onStop, observer should not be called!
+ mLiveData.setValue("b");
+ }
+ });
+ Observer<String> observer = (Observer<String>) mock(Observer.class);
+ mLiveData.setValue("a");
+ mLiveData.observe(mOwner, observer);
+ verify(observer).onChanged("a");
+ mRegistry.handleLifecycleEvent(ON_PAUSE);
+ mRegistry.handleLifecycleEvent(ON_STOP);
+ verify(observer, never()).onChanged("b");
+
+ mRegistry.handleLifecycleEvent(ON_RESUME);
+ verify(observer).onChanged("b");
+ }
+
+ @Test
+ public void testNotCallInactiveWithObserveForever() {
+ mRegistry.handleLifecycleEvent(ON_START);
+ Observer<String> observer = (Observer<String>) mock(Observer.class);
+ Observer<String> observer2 = (Observer<String>) mock(Observer.class);
+ mLiveData.observe(mOwner, observer);
+ mLiveData.observeForever(observer2);
+ verify(mActiveObserversChanged).onCall(true);
+ reset(mActiveObserversChanged);
+ mRegistry.handleLifecycleEvent(ON_STOP);
+ verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+ mRegistry.handleLifecycleEvent(ON_START);
+ verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+ }
+
+ @Test
+ public void testRemoveDuringAddition() {
+ mRegistry.handleLifecycleEvent(ON_START);
+ mLiveData.setValue("bla");
+ mLiveData.observeForever(new Observer<String>() {
+ @Override
+ public void onChanged(@Nullable String s) {
+ mLiveData.removeObserver(this);
+ }
+ });
+ assertThat(mLiveData.hasActiveObservers(), is(false));
+ InOrder inOrder = Mockito.inOrder(mActiveObserversChanged);
+ inOrder.verify(mActiveObserversChanged).onCall(true);
+ inOrder.verify(mActiveObserversChanged).onCall(false);
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ @Test
+ public void testRemoveDuringBringingUpToState() {
+ mLiveData.setValue("bla");
+ mLiveData.observeForever(new Observer<String>() {
+ @Override
+ public void onChanged(@Nullable String s) {
+ mLiveData.removeObserver(this);
+ }
+ });
+ mRegistry.handleLifecycleEvent(ON_RESUME);
+ assertThat(mLiveData.hasActiveObservers(), is(false));
+ InOrder inOrder = Mockito.inOrder(mActiveObserversChanged);
+ inOrder.verify(mActiveObserversChanged).onCall(true);
+ inOrder.verify(mActiveObserversChanged).onCall(false);
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ @Test
+ public void setValue_neverActive_observerOnChangedNotCalled() {
+ Observer<String> observer = (Observer<String>) mock(Observer.class);
+ mLiveData.observe(mOwner, observer);
+
+ mLiveData.setValue("1");
+
+ verify(observer, never()).onChanged(anyString());
+ }
+
+ @Test
+ public void setValue_twoObserversTwoStartedOwners_onChangedCalledOnBoth() {
+ Observer<String> observer1 = mock(Observer.class);
+ Observer<String> observer2 = mock(Observer.class);
+
+ mLiveData.observe(mOwner, observer1);
+ mLiveData.observe(mOwner2, observer2);
+
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+ mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+ mLiveData.setValue("1");
+
+ verify(observer1).onChanged("1");
+ verify(observer2).onChanged("1");
+ }
+
+ @Test
+ public void setValue_twoObserversOneStartedOwner_onChangedCalledOnOneCorrectObserver() {
+ Observer<String> observer1 = mock(Observer.class);
+ Observer<String> observer2 = mock(Observer.class);
+
+ mLiveData.observe(mOwner, observer1);
+ mLiveData.observe(mOwner2, observer2);
+
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+ mLiveData.setValue("1");
+
+ verify(observer1).onChanged("1");
+ verify(observer2, never()).onChanged(anyString());
+ }
+
+ @Test
+ public void setValue_twoObserversBothStartedAfterSetValue_onChangedCalledOnBoth() {
+ Observer<String> observer1 = mock(Observer.class);
+ Observer<String> observer2 = mock(Observer.class);
+
+ mLiveData.observe(mOwner, observer1);
+ mLiveData.observe(mOwner2, observer2);
+
+ mLiveData.setValue("1");
+
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+ mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+ verify(observer1).onChanged("1");
+ verify(observer1).onChanged("1");
+ }
+
+ @Test
+ public void setValue_twoObserversOneStartedAfterSetValue_onChangedCalledOnCorrectObserver() {
+ Observer<String> observer1 = mock(Observer.class);
+ Observer<String> observer2 = mock(Observer.class);
+
+ mLiveData.observe(mOwner, observer1);
+ mLiveData.observe(mOwner2, observer2);
+
+ mLiveData.setValue("1");
+
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+ verify(observer1).onChanged("1");
+ verify(observer2, never()).onChanged(anyString());
+ }
+
+ @Test
+ public void setValue_twoObserversOneStarted_liveDataBecomesActive() {
+ Observer<String> observer1 = mock(Observer.class);
+ Observer<String> observer2 = mock(Observer.class);
+
+ mLiveData.observe(mOwner, observer1);
+ mLiveData.observe(mOwner2, observer2);
+
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+ verify(mActiveObserversChanged).onCall(true);
+ }
+
+ @Test
+ public void setValue_twoObserversOneStopped_liveDataStaysActive() {
+ Observer<String> observer1 = mock(Observer.class);
+ Observer<String> observer2 = mock(Observer.class);
+
+ mLiveData.observe(mOwner, observer1);
+ mLiveData.observe(mOwner2, observer2);
+
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+ mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+ verify(mActiveObserversChanged).onCall(true);
+
+ reset(mActiveObserversChanged);
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
+
+ verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+ }
+
+ @Test
+ public void setValue_lifecycleIsCreatedNoEvent_liveDataBecomesInactiveAndObserverNotCalled() {
+
+ // Arrange.
+
+ mLiveData.observe(mOwner3, mObserver3);
+
+ GenericLifecycleObserver lifecycleObserver = getGenericLifecycleObserver(mLifecycle3);
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+ lifecycleObserver.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+
+ reset(mActiveObserversChanged);
+ reset(mObserver3);
+
+ // Act.
+
+ mLiveData.setValue("1");
+
+ // Assert.
+
+ verify(mActiveObserversChanged).onCall(false);
+ verify(mObserver3, never()).onChanged(anyString());
+ }
+
+ /*
+ * Arrange: LiveData was made inactive via SetValue (because the Lifecycle it was
+ * observing was in the CREATED state and no event was dispatched).
+ * Act: Lifecycle enters Started state and dispatches event.
+ * Assert: LiveData becomes active and dispatches new value to observer.
+ */
+ @Test
+ public void test_liveDataInactiveViaSetValueThenLifecycleResumes() {
+
+ // Arrange.
+
+ mLiveData.observe(mOwner3, mObserver3);
+
+ GenericLifecycleObserver lifecycleObserver = getGenericLifecycleObserver(mLifecycle3);
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+ lifecycleObserver.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+ mLiveData.setValue("1");
+
+ reset(mActiveObserversChanged);
+ reset(mObserver3);
+
+ // Act.
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+ lifecycleObserver.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+
+ // Assert.
+
+ verify(mActiveObserversChanged).onCall(true);
+ verify(mObserver3).onChanged("1");
+ }
+
+ /*
+ * Arrange: One of two Lifecycles enter the CREATED state without sending an event.
+ * Act: Lifecycle's setValue method is called with new value.
+ * Assert: LiveData stays active and new value is dispatched to Lifecycle that is still at least
+ * STARTED.
+ */
+ @Test
+ public void setValue_oneOfTwoLifecyclesAreCreatedNoEvent() {
+
+ // Arrange.
+
+ mLiveData.observe(mOwner3, mObserver3);
+ mLiveData.observe(mOwner4, mObserver4);
+
+ GenericLifecycleObserver lifecycleObserver3 = getGenericLifecycleObserver(mLifecycle3);
+ GenericLifecycleObserver lifecycleObserver4 = getGenericLifecycleObserver(mLifecycle4);
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+ when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+ lifecycleObserver3.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+ lifecycleObserver4.onStateChanged(mOwner4, Lifecycle.Event.ON_START);
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+
+ reset(mActiveObserversChanged);
+ reset(mObserver3);
+ reset(mObserver4);
+
+ // Act.
+
+ mLiveData.setValue("1");
+
+ // Assert.
+
+ verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+ verify(mObserver3, never()).onChanged(anyString());
+ verify(mObserver4).onChanged("1");
+ }
+
+ /*
+ * Arrange: Two observed Lifecycles enter the CREATED state without sending an event.
+ * Act: Lifecycle's setValue method is called with new value.
+ * Assert: LiveData becomes inactive and nothing is dispatched to either observer.
+ */
+ @Test
+ public void setValue_twoLifecyclesAreCreatedNoEvent() {
+
+ // Arrange.
+
+ mLiveData.observe(mOwner3, mObserver3);
+ mLiveData.observe(mOwner4, mObserver4);
+
+ GenericLifecycleObserver lifecycleObserver3 = getGenericLifecycleObserver(mLifecycle3);
+ GenericLifecycleObserver lifecycleObserver4 = getGenericLifecycleObserver(mLifecycle4);
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+ when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+ lifecycleObserver3.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+ lifecycleObserver4.onStateChanged(mOwner4, Lifecycle.Event.ON_START);
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+ when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+
+ reset(mActiveObserversChanged);
+ reset(mObserver3);
+ reset(mObserver4);
+
+ // Act.
+
+ mLiveData.setValue("1");
+
+ // Assert.
+
+ verify(mActiveObserversChanged).onCall(false);
+ verify(mObserver3, never()).onChanged(anyString());
+ verify(mObserver3, never()).onChanged(anyString());
+ }
+
+ /*
+ * Arrange: LiveData was made inactive via SetValue (because both Lifecycles it was
+ * observing were in the CREATED state and no event was dispatched).
+ * Act: One Lifecycle enters STARTED state and dispatches lifecycle event.
+ * Assert: LiveData becomes active and dispatches new value to observer associated with started
+ * Lifecycle.
+ */
+ @Test
+ public void test_liveDataInactiveViaSetValueThenOneLifecycleResumes() {
+
+ // Arrange.
+
+ mLiveData.observe(mOwner3, mObserver3);
+ mLiveData.observe(mOwner4, mObserver4);
+
+ GenericLifecycleObserver lifecycleObserver3 = getGenericLifecycleObserver(mLifecycle3);
+ GenericLifecycleObserver lifecycleObserver4 = getGenericLifecycleObserver(mLifecycle4);
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+ when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+ lifecycleObserver3.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+ lifecycleObserver4.onStateChanged(mOwner4, Lifecycle.Event.ON_START);
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+ when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+
+ mLiveData.setValue("1");
+
+ reset(mActiveObserversChanged);
+ reset(mObserver3);
+ reset(mObserver4);
+
+ // Act.
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+ lifecycleObserver3.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+
+ // Assert.
+
+ verify(mActiveObserversChanged).onCall(true);
+ verify(mObserver3).onChanged("1");
+ verify(mObserver4, never()).onChanged(anyString());
+ }
+
+ @Test
+ public void nestedForeverObserver() {
+ mLiveData.setValue(".");
+ mLiveData.observeForever(new Observer<String>() {
+ @Override
+ public void onChanged(@Nullable String s) {
+ mLiveData.observeForever(mock(Observer.class));
+ mLiveData.removeObserver(this);
+ }
+ });
+ verify(mActiveObserversChanged, only()).onCall(true);
+ }
+
+ @Test
+ public void readdForeverObserver() {
+ Observer observer = mock(Observer.class);
+ mLiveData.observeForever(observer);
+ mLiveData.observeForever(observer);
+ mLiveData.removeObserver(observer);
+ assertThat(mLiveData.hasObservers(), is(false));
+ }
+
+ private GenericLifecycleObserver getGenericLifecycleObserver(Lifecycle lifecycle) {
+ ArgumentCaptor<GenericLifecycleObserver> captor =
+ ArgumentCaptor.forClass(GenericLifecycleObserver.class);
+ verify(lifecycle).addObserver(captor.capture());
+ return (captor.getValue());
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ static class PublicLiveData<T> extends LiveData<T> {
+ // cannot spy due to internal calls
+ public MethodExec activeObserversChanged;
+
+ @Override
+ protected void onActive() {
+ if (activeObserversChanged != null) {
+ activeObserversChanged.onCall(true);
+ }
+ }
+
+ @Override
+ protected void onInactive() {
+ if (activeObserversChanged != null) {
+ activeObserversChanged.onCall(false);
+ }
+ }
+ }
+
+ private class FailReentranceObserver<T> implements Observer<T> {
+ @Override
+ public void onChanged(@Nullable T t) {
+ assertThat(mInObserver, is(false));
+ }
+ }
+
+ interface MethodExec {
+ void onCall(boolean value);
+ }
+}
diff --git a/lifecycle/livedata-core/src/test/java/android/arch/lifecycle/ThreadedLiveDataTest.java b/lifecycle/livedata-core/src/test/java/android/arch/lifecycle/ThreadedLiveDataTest.java
new file mode 100644
index 0000000..3366641
--- /dev/null
+++ b/lifecycle/livedata-core/src/test/java/android/arch/lifecycle/ThreadedLiveDataTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.arch.core.executor.JunitTaskExecutorRule;
+import android.arch.core.executor.TaskExecutor;
+import android.support.annotation.Nullable;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(JUnit4.class)
+public class ThreadedLiveDataTest {
+
+ private static final int TIMEOUT_SECS = 3;
+
+ @Rule
+ public JunitTaskExecutorRule mTaskExecutorRule = new JunitTaskExecutorRule(1, false);
+
+ private LiveData<String> mLiveData;
+ private LifecycleOwner mLifecycleOwner;
+ private LifecycleRegistry mRegistry;
+
+ @Before
+ public void init() {
+ mLiveData = new MutableLiveData<>();
+ mLifecycleOwner = mock(LifecycleOwner.class);
+ mRegistry = new LifecycleRegistry(mLifecycleOwner);
+ when(mLifecycleOwner.getLifecycle()).thenReturn(mRegistry);
+ }
+
+ @Test
+ public void testPostValue() throws InterruptedException {
+ final TaskExecutor taskExecutor = mTaskExecutorRule.getTaskExecutor();
+ final CountDownLatch finishTestLatch = new CountDownLatch(1);
+ final Observer<String> observer = new Observer<String>() {
+ @Override
+ public void onChanged(@Nullable String newValue) {
+ try {
+ assertThat(taskExecutor.isMainThread(), is(true));
+ assertThat(newValue, is("success"));
+ } finally {
+ finishTestLatch.countDown();
+ }
+ }
+ };
+ taskExecutor.executeOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ mRegistry.handleLifecycleEvent(ON_START);
+ mLiveData.observe(mLifecycleOwner, observer);
+ final CountDownLatch latch = new CountDownLatch(1);
+ taskExecutor.executeOnDiskIO(new Runnable() {
+ @Override
+ public void run() {
+ mLiveData.postValue("fail");
+ mLiveData.postValue("success");
+ latch.countDown();
+ }
+ });
+ try {
+ assertThat(latch.await(TIMEOUT_SECS, TimeUnit.SECONDS), is(true));
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ });
+ assertThat(finishTestLatch.await(TIMEOUT_SECS, TimeUnit.SECONDS), is(true));
+ }
+}