diff --git a/app/build.gradle b/app/build.gradle index b4a2af1c8c..009ac1f1e5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -119,6 +119,10 @@ repositories { maven { url "https://jitpack.io" } } +configurations.all { + exclude group: "androidx.lifecycle", module: "lifecycle-livedata" +} + dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) @@ -178,7 +182,6 @@ dependencies { // https://mvnrepository.com/artifact/androidx.paging/paging-runtime implementation "androidx.paging:paging-runtime:$paging_version" - // https://mvnrepository.com/artifact/androidx.preference/preference implementation "androidx.preference:preference:$preference_version" diff --git a/app/src/main/java/androidx/lifecycle/ComputableLiveData.java b/app/src/main/java/androidx/lifecycle/ComputableLiveData.java new file mode 100644 index 0000000000..837a35dbf9 --- /dev/null +++ b/app/src/main/java/androidx/lifecycle/ComputableLiveData.java @@ -0,0 +1,150 @@ +/* + * 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 androidx.lifecycle; + +import androidx.annotation.MainThread; +import androidx.annotation.NonNull; +import androidx.annotation.RestrictTo; +import androidx.annotation.VisibleForTesting; +import androidx.annotation.WorkerThread; +import androidx.arch.core.executor.ArchTaskExecutor; + +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * A LiveData class that can be invalidated & computed when there are active observers. + *
+ * It can be invalidated via {@link #invalidate()}, which will result in a call to + * {@link #compute()} if there are active observers (or when they start observing) + *
+ * This is an internal class for now, might be public if we see the necessity.
+ *
+ * @param
+ * When there are active observers, this will trigger a call to {@link #compute()}.
+ */
+ public void invalidate() {
+ ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);
+ }
+
+ // TODO https://issuetracker.google.com/issues/112197238
+ @SuppressWarnings({"WeakerAccess", "UnknownNullness"})
+ @WorkerThread
+ protected abstract T compute();
+}
diff --git a/app/src/main/java/androidx/lifecycle/LiveData.java b/app/src/main/java/androidx/lifecycle/LiveData.java
new file mode 100644
index 0000000000..b03476c8c6
--- /dev/null
+++ b/app/src/main/java/androidx/lifecycle/LiveData.java
@@ -0,0 +1,447 @@
+/*
+ * 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 androidx.lifecycle;
+
+import static androidx.lifecycle.Lifecycle.State.DESTROYED;
+import static androidx.lifecycle.Lifecycle.State.STARTED;
+
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.arch.core.executor.ArchTaskExecutor;
+import androidx.arch.core.internal.SafeIterableMap;
+
+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)}.
+ *
+ * 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.
+ *
+ *
+ * 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.
+ *
+ * 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
+ * The observer will only receive events if the owner is in {@link Lifecycle.State#STARTED}
+ * or {@link Lifecycle.State#RESUMED} state (active).
+ *
+ * If the owner moves to the {@link Lifecycle.State#DESTROYED} state, the observer will
+ * automatically be removed.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * If the given owner is already in {@link Lifecycle.State#DESTROYED} state, LiveData
+ * ignores the call.
+ *
+ * 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 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);
+ }
+
+ /**
+ * 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.
+ *
+ * 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 super T> observer) {
+ assertMainThread("observeForever");
+ 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 super 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
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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).
+ *
+ * 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 super 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 super T> mObserver;
+ boolean mActive;
+ int mLastVersion = START_VERSION;
+
+ ObserverWrapper(Observer super 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 super 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/app/src/main/java/androidx/lifecycle/MediatorLiveData.java b/app/src/main/java/androidx/lifecycle/MediatorLiveData.java
new file mode 100644
index 0000000000..58261ddac0
--- /dev/null
+++ b/app/src/main/java/androidx/lifecycle/MediatorLiveData.java
@@ -0,0 +1,156 @@
+/*
+ * 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 androidx.lifecycle;
+
+import androidx.annotation.CallSuper;
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.arch.core.internal.SafeIterableMap;
+
+import java.util.Map;
+
+/**
+ * {@link LiveData} subclass which may observe other {@code LiveData} objects and react on
+ * {@code OnChanged} events from them.
+ *
+ * This class correctly propagates its active/inactive states down to source {@code LiveData}
+ * objects.
+ *
+ * Consider the following scenario: we have 2 instances of {@code LiveData}, let's name them
+ * {@code liveData1} and {@code liveData2}, and we want to merge their emissions in one object:
+ * {@code liveDataMerger}. Then, {@code liveData1} and {@code liveData2} will become sources for
+ * the {@code MediatorLiveData liveDataMerger} and every time {@code onChanged} callback
+ * is called for either of them, we set a new value in {@code liveDataMerger}.
+ *
+ *
+ * Let's consider that we only want 10 values emitted by {@code liveData1}, to be
+ * merged in the {@code liveDataMerger}. Then, after 10 values, we can stop listening to {@code
+ * liveData1} and remove it as a source.
+ *
+ * {@code onChanged} callback will be called only when this {@code MediatorLiveData} is active.
+ * If the given LiveData is already added as a source but with a different Observer,
+ * {@link IllegalArgumentException} will be thrown.
+ *
+ * @param source the {@code LiveData} to listen to
+ * @param onChanged The observer that will receive the events
+ * @param
+ * These methods permit functional composition and delegation of {@link LiveData} instances. The
+ * transformations are calculated lazily, and will run only when the returned {@link LiveData} is
+ * observed. Lifecycle behavior is propagated from the input {@code source} {@link LiveData} to the
+ * returned one.
+ */
+@SuppressWarnings("WeakerAccess")
+public class Transformations {
+
+ private Transformations() {
+ }
+
+ /**
+ * Returns a {@code LiveData} mapped from the input {@code source} {@code LiveData} by applying
+ * {@code mapFunction} to each value set on {@code source}.
+ *
+ * This method is analogous to {@link io.reactivex.Observable#map}.
+ *
+ * {@code transform} will be executed on the main thread.
+ *
+ * Here is an example mapping a simple {@code User} struct in a {@code LiveData} to a
+ * {@code LiveData} containing their full name as a {@code String}.
+ *
+ *
+ * The returned {@code LiveData} delegates to the most recent {@code LiveData} created by
+ * calling {@code switchMapFunction} with the most recent value set to {@code source}, without
+ * changing the reference. In this way, {@code switchMapFunction} can change the 'backing'
+ * {@code LiveData} transparently to any observer registered to the {@code LiveData} returned
+ * by {@code switchMap()}.
+ *
+ * Note that when the backing {@code LiveData} is switched, no further values from the older
+ * {@code LiveData} will be set to the output {@code LiveData}. In this way, the method is
+ * analogous to {@link io.reactivex.Observable#switchMap}.
+ *
+ * {@code switchMapFunction} will be executed on the main thread.
+ *
+ * Here is an example class that holds a typed-in name of a user
+ * {@code String} (such as from an {@code EditText}) in a {@link MutableLiveData} and
+ * returns a {@code LiveData} containing a List of {@code User} objects for users that have
+ * that name. It populates that {@code LiveData} by requerying a repository-pattern object
+ * each time the typed name changes.
+ *
+ * This {@code ViewModel} would permit the observing UI to update "live" as the user ID text
+ * changes.
+ *
+ *
+ * liveData.postValue("a");
+ * liveData.setValue("b");
+ *
+ * The value "b" would be set at first and later the main thread would override it with
+ * the value "a".
+ *
+ * LiveData
+ *
+ * liveDataMerger.addSource(liveData1, new Observer
+ *
+ * @param The type of data hold by {@code source} LiveData
+ */
+ @MainThread
+ public void addSource(@NonNull LiveData source, @NonNull Observer super S> onChanged) {
+ Source 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();
+ }
+ }
+
+ /**
+ * Stops to listen the given {@code LiveData}.
+ *
+ * @param toRemote {@code LiveData} to stop to listen
+ * @param the type of data hold by {@code source} LiveData
+ */
+ @MainThread
+ public void removeSource(@NonNull LiveData toRemote) {
+ Source> source = mSources.remove(toRemote);
+ if (source != null) {
+ source.unplug();
+ }
+ }
+
+ @CallSuper
+ @Override
+ protected void onActive() {
+ for (Map.Entry
+ * LiveData
+ *
+ * @param source the {@code LiveData} to map from
+ * @param mapFunction a function to apply to each value set on {@code source} in order to set
+ * it
+ * on the output {@code LiveData}
+ * @param
+ * class UserViewModel extends AndroidViewModel {
+ * MutableLiveData
+ *
+ * @param source the {@code LiveData} to map from
+ * @param switchMapFunction a function to apply to each value set on {@code source} to create a
+ * new delegate {@code LiveData} for the returned one
+ * @param > getUsersWithNameLiveData() {
+ * return Transformations.switchMap(
+ * nameQueryLiveData,
+ * name -> myDataSource.getUsersWithNameLiveData(name));
+ * }
+ *
+ * void setNameQuery(String name) {
+ * this.nameQueryLiveData.setValue(name);
+ * }
+ * }
+ *