diff --git a/app/build.gradle b/app/build.gradle
index 4c8a9f6bb6..ef92c4eeab 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -427,7 +427,7 @@ dependencies {
def lbm_version = "1.1.0"
def swiperefresh_version = "1.2.0-alpha01"
def documentfile_version = "1.1.0-alpha01"
- def lifecycle_version = "2.5.1" // 2.6.0
+ def lifecycle_version = "2.6.0"
def lifecycle_extensions_version = "2.2.0"
def room_version = "2.4.3" // 2.5.0
def sqlite_version = "2.3.0"
diff --git a/app/src/main/java/androidx/lifecycle/ComputableLiveData.java b/app/src/main/java/androidx/lifecycle/ComputableLiveData.java
deleted file mode 100644
index 290d9bad8b..0000000000
--- a/app/src/main/java/androidx/lifecycle/ComputableLiveData.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * 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 The type of the live data
- * @hide internal
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-public abstract class ComputableLiveData {
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final Executor mExecutor;
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final LiveData mLiveData;
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final AtomicBoolean mInvalid = new AtomicBoolean(true);
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final AtomicBoolean mComputing = new AtomicBoolean(false);
-
- /**
- * Creates a computable live data that computes values on the arch IO thread executor.
- */
- @SuppressWarnings("WeakerAccess")
- public ComputableLiveData() {
- this(ArchTaskExecutor.getIOThreadExecutor());
- }
-
- /**
- * Creates a computable live data that computes values on the specified executor.
- *
- * @param executor Executor that is used to compute new LiveData values.
- */
- @SuppressWarnings("WeakerAccess")
- public ComputableLiveData(@NonNull Executor executor) {
- mExecutor = executor;
- mLiveData = new LiveData() {
- @Override
- protected void onActive() {
- mExecutor.execute(mRefreshRunnable);
- }
- };
- }
-
- /**
- * Returns the LiveData managed by this class.
- *
- * @return A LiveData that is controlled by ComputableLiveData.
- */
- @SuppressWarnings("WeakerAccess")
- @NonNull
- public LiveData getLiveData() {
- return mLiveData;
- }
-
- @VisibleForTesting
- final Runnable mRefreshRunnable = new Runnable() {
- @WorkerThread
- @Override
- public void run() {
- boolean computed;
- do {
- computed = false;
- // compute can happen only in 1 thread but no reason to lock others.
- if (mComputing.compareAndSet(false, true)) {
- // as long as it is invalid, keep computing.
- try {
- T value = null;
- boolean once = true;
- long last = android.os.SystemClock.elapsedRealtime();
- while (mInvalid.compareAndSet(true, false)) {
- long now = android.os.SystemClock.elapsedRealtime();
- if (value != null && (once || last + 2500 < now)) {
- eu.faircode.email.Log.i(mLiveData + " post once=" + once + " age=" + (now - last) + " ms");
- once = false;
- last = now;
- mLiveData.postValue(value);
- }
- computed = true;
- value = compute();
- }
- if (computed) {
- mLiveData.postValue(value);
- }
- } catch (Throwable ex) {
- // java.lang.IllegalStateException: Couldn't read row xxx column yyy
- eu.faircode.email.Log.e(ex);
- mInvalid.set(true);
- } finally {
- // release compute lock
- mComputing.set(false);
- }
- }
- // check invalid after releasing compute lock to avoid the following scenario.
- // Thread A runs compute()
- // Thread A checks invalid, it is false
- // Main thread sets invalid to true
- // Thread B runs, fails to acquire compute lock and skips
- // Thread A releases compute lock
- // We've left invalid in set state. The check below recovers.
- } while (computed && mInvalid.get());
- }
- };
-
- // invalidation check always happens on the main thread
- @VisibleForTesting
- final Runnable mInvalidationRunnable = new Runnable() {
- @MainThread
- @Override
- public void run() {
- boolean isActive = mLiveData.hasActiveObservers();
- if (mInvalid.compareAndSet(false, true)) {
- if (isActive) {
- mExecutor.execute(mRefreshRunnable);
- }
- }
- }
- };
-
- /**
- * Invalidates the LiveData.
- *
- * 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/ComputableLiveData.kt b/app/src/main/java/androidx/lifecycle/ComputableLiveData.kt
new file mode 100644
index 0000000000..0278c2cae0
--- /dev/null
+++ b/app/src/main/java/androidx/lifecycle/ComputableLiveData.kt
@@ -0,0 +1,131 @@
+/*
+ * 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.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 [invalidate], which will result in a call to
+ * [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 The type of the live data
+ * @hide internal
+*/
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+abstract class ComputableLiveData @JvmOverloads
+/**
+ * Creates a computable live data that computes values on the specified executor
+ * or the arch IO thread executor by default.
+ *
+ * @param executor Executor that is used to compute new LiveData values.
+ */
+constructor(
+ internal val executor: Executor = ArchTaskExecutor.getIOThreadExecutor()
+) {
+
+ private val _liveData: LiveData =
+ object : LiveData() {
+ override fun onActive() {
+ executor.execute(refreshRunnable)
+ }
+ }
+ /**
+ * The LiveData managed by this class.
+ */
+ open val liveData: LiveData = _liveData
+ internal val invalid = AtomicBoolean(true)
+ internal val computing = AtomicBoolean(false)
+
+ @JvmField
+ @VisibleForTesting
+ internal val refreshRunnable = Runnable {
+ var computed: Boolean
+ do {
+ computed = false
+ // compute can happen only in 1 thread but no reason to lock others.
+ if (computing.compareAndSet(false, true)) {
+ // as long as it is invalid, keep computing.
+ try {
+ var value: T? = null
+ var once = true;
+ var last = android.os.SystemClock.elapsedRealtime()
+ while (invalid.compareAndSet(true, false)) {
+ var now = android.os.SystemClock.elapsedRealtime()
+ if (value != null && (once || last + 2500 < now)) {
+ eu.faircode.email.Log.i(liveData.toString() + " post once=" + once + " age=" + (now - last) + " ms")
+ once = false;
+ last = now;
+ liveData.postValue(value);
+ }
+ computed = true
+ value = compute()
+ }
+ if (computed) {
+ liveData.postValue(value)
+ }
+ } catch (ex: Throwable) {
+ // java.lang.IllegalStateException: Couldn't read row xxx column yyy
+ eu.faircode.email.Log.e(ex);
+ invalid.set(true);
+ } finally {
+ // release compute lock
+ computing.set(false)
+ }
+ }
+ // check invalid after releasing compute lock to avoid the following scenario.
+ // Thread A runs compute()
+ // Thread A checks invalid, it is false
+ // Main thread sets invalid to true
+ // Thread B runs, fails to acquire compute lock and skips
+ // Thread A releases compute lock
+ // We've left invalid in set state. The check below recovers.
+ } while (computed && invalid.get())
+ }
+
+ // invalidation check always happens on the main thread
+ @JvmField
+ @VisibleForTesting
+ internal val invalidationRunnable = Runnable {
+ val isActive = liveData.hasActiveObservers()
+ if (invalid.compareAndSet(false, true)) {
+ if (isActive) {
+ executor.execute(refreshRunnable)
+ }
+ }
+ }
+
+ /**
+ * Invalidates the LiveData.
+ *
+ * When there are active observers, this will trigger a call to [.compute].
+ */
+ open fun invalidate() {
+ ArchTaskExecutor.getInstance().executeOnMainThread(invalidationRunnable)
+ }
+
+ // TODO https://issuetracker.google.com/issues/112197238
+ @WorkerThread
+ protected abstract fun compute(): T
+}
\ No newline at end of file
diff --git a/app/src/main/java/androidx/lifecycle/LiveData.java b/app/src/main/java/androidx/lifecycle/LiveData.java
index a694fcd9c8..10b9fac733 100644
--- a/app/src/main/java/androidx/lifecycle/LiveData.java
+++ b/app/src/main/java/androidx/lifecycle/LiveData.java
@@ -311,10 +311,11 @@ public abstract class LiveData {
/**
* 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
+ * @return the current value or null if {@link #isInitialized()} is false
*/
@SuppressWarnings("unchecked")
@Nullable
@@ -326,6 +327,20 @@ public abstract class LiveData {
return null;
}
+ /**
+ * Returns whether an explicit value has been set on this LiveData. If this returns
+ * true
, then the current value can be retrieved from {@link #getValue()}.
+ *
+ * Note that calling this method on a background thread may still result in this method
+ * returning false
even if a call to {@link #postValue(Object)} is being
+ * processed.
+ *
+ * @return whether an explicit value has been set on this LiveData
+ */
+ public boolean isInitialized() {
+ return mData != NOT_SET;
+ }
+
int getVersion() {
return mVersion;
}
diff --git a/app/src/main/java/androidx/lifecycle/MediatorLiveData.java b/app/src/main/java/androidx/lifecycle/MediatorLiveData.java
index f9fc4f2ecf..1fe6ced11c 100644
--- a/app/src/main/java/androidx/lifecycle/MediatorLiveData.java
+++ b/app/src/main/java/androidx/lifecycle/MediatorLiveData.java
@@ -38,25 +38,25 @@ import java.util.Map;
* is called for either of them, we set a new value in {@code liveDataMerger}.
*
*
- * LiveData liveData1 = ...;
- * LiveData liveData2 = ...;
+ * LiveData<Integer> liveData1 = ...;
+ * LiveData<Integer> liveData2 = ...;
*
- * MediatorLiveData liveDataMerger = new MediatorLiveData<>();
- * liveDataMerger.addSource(liveData1, value -> liveDataMerger.setValue(value));
- * liveDataMerger.addSource(liveData2, value -> liveDataMerger.setValue(value));
+ * MediatorLiveData<Integer> liveDataMerger = new MediatorLiveData<>();
+ * liveDataMerger.addSource(liveData1, value -> liveDataMerger.setValue(value));
+ * liveDataMerger.addSource(liveData2, value -> liveDataMerger.setValue(value));
*
*
* 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.
*
- * liveDataMerger.addSource(liveData1, new Observer() {
+ * liveDataMerger.addSource(liveData1, new Observer<Integer>() {
* private int count = 1;
*
* {@literal @}Override public void onChanged(@Nullable Integer s) {
* count++;
* liveDataMerger.setValue(s);
- * if (count > 10) {
+ * if (count > 10) {
* liveDataMerger.removeSource(liveData1);
* }
* }
@@ -70,8 +70,24 @@ public class MediatorLiveData extends MutableLiveData {
private SafeIterableMap, Source>> mSources = new SafeIterableMap<>();
/**
- * Starts to listen the given {@code source} LiveData, {@code onChanged} observer will be called
- * when {@code source} value was changed.
+ * Creates a MediatorLiveData with no value assigned to it.
+ */
+ public MediatorLiveData() {
+ super();
+ }
+
+ /**
+ * Creates a MediatorLiveData initialized with the given {@code value}.
+ *
+ * @param value initial value
+ */
+ public MediatorLiveData(T value) {
+ super(value);
+ }
+
+ /**
+ * Starts to listen to the given {@code source} LiveData, {@code onChanged} observer will be
+ * called when {@code source} value was changed.
*
* {@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,
diff --git a/app/src/main/java/androidx/lifecycle/Observer.java b/app/src/main/java/androidx/lifecycle/Observer.kt
similarity index 72%
rename from app/src/main/java/androidx/lifecycle/Observer.java
rename to app/src/main/java/androidx/lifecycle/Observer.kt
index 30afeedf07..2df6295d87 100644
--- a/app/src/main/java/androidx/lifecycle/Observer.java
+++ b/app/src/main/java/androidx/lifecycle/Observer.kt
@@ -13,20 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-package androidx.lifecycle;
+package androidx.lifecycle
/**
- * A simple callback that can receive from {@link LiveData}.
- *
- * @param The type of the parameter
+ * A simple callback that can receive from [LiveData].
*
* @see LiveData LiveData - for a usage description.
- */
-public interface Observer {
+*/
+fun interface Observer {
+
/**
- * Called when the data is changed.
- * @param t The new data
+ * Called when the data is changed is changed to [value].
*/
- void onChanged(T t);
-}
+ fun onChanged(value: T)
+}
\ No newline at end of file
diff --git a/app/src/main/java/androidx/lifecycle/Transformations.java b/app/src/main/java/androidx/lifecycle/Transformations.java
deleted file mode 100644
index f687e65c8d..0000000000
--- a/app/src/main/java/androidx/lifecycle/Transformations.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * 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.Nullable;
-import androidx.arch.core.util.Function;
-
-/**
- * Transformation methods for {@link LiveData}.
- *
- * 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}.
- *
- *
- * LiveData<User> userLiveData = ...;
- * LiveData<String> userFullNameLiveData =
- * Transformations.map(
- * userLiveData,
- * user -> user.firstName + user.lastName);
- * });
- *
- *
- * @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 the generic type parameter of {@code source}
- * @param the generic type parameter of the returned {@code LiveData}
- * @return a LiveData mapped from {@code source} to type {@code } by applying
- * {@code mapFunction} to each value set.
- */
- @MainThread
- @NonNull
- public static LiveData map(
- @NonNull LiveData source,
- @NonNull final Function mapFunction) {
- final MediatorLiveData result = new MediatorLiveData<>();
- result.addSource(source, new Observer() {
- @Override
- public void onChanged(@Nullable X x) {
- result.setValue(mapFunction.apply(x));
- }
- });
- return result;
- }
-
- /**
- * Returns a {@code LiveData} mapped from the input {@code source} {@code LiveData} by applying
- * {@code switchMapFunction} to each value set on {@code source}.
- *
- * 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.
- *
- *
- * class UserViewModel extends AndroidViewModel {
- * MutableLiveData<String> nameQueryLiveData = ...
- *
- * LiveData<List<String>> getUsersWithNameLiveData() {
- * return Transformations.switchMap(
- * nameQueryLiveData,
- * name -> myDataSource.getUsersWithNameLiveData(name));
- * }
- *
- * void setNameQuery(String name) {
- * this.nameQueryLiveData.setValue(name);
- * }
- * }
- *
- *
- * @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 the generic type parameter of {@code source}
- * @param the generic type parameter of the returned {@code LiveData}
- * @return a LiveData mapped from {@code source} to type {@code } by delegating
- * to the LiveData returned by applying {@code switchMapFunction} to each
- * value set
- */
- @MainThread
- @NonNull
- public static LiveData switchMap(
- @NonNull LiveData source,
- @NonNull final Function> switchMapFunction) {
- final MediatorLiveData result = new MediatorLiveData<>();
- result.addSource(source, new Observer() {
- LiveData mSource;
-
- @Override
- public void onChanged(@Nullable X x) {
- LiveData newLiveData = switchMapFunction.apply(x);
- if (mSource == newLiveData) {
- return;
- }
- if (mSource != null) {
- result.removeSource(mSource);
- }
- mSource = newLiveData;
- if (mSource != null) {
- result.addSource(mSource, new Observer() {
- @Override
- public void onChanged(@Nullable Y y) {
- result.setValue(y);
- }
- });
- }
- }
- });
- return result;
- }
-
- /**
- * Creates a new {@link LiveData} object that does not emit a value until the source LiveData
- * value has been changed. The value is considered changed if {@code equals()} yields
- * {@code false}.
- *
- * @param source the input {@link LiveData}
- * @param the generic type parameter of {@code source}
- * @return a new {@link LiveData} of type {@code X}
- */
- @MainThread
- @NonNull
- public static LiveData distinctUntilChanged(@NonNull LiveData source) {
- final MediatorLiveData outputLiveData = new MediatorLiveData<>();
- outputLiveData.addSource(source, new Observer() {
-
- boolean mFirstTime = true;
-
- @Override
- public void onChanged(X currentValue) {
- final X previousValue = outputLiveData.getValue();
- if (mFirstTime
- || (previousValue == null && currentValue != null)
- || (previousValue != null && !previousValue.equals(currentValue))) {
- mFirstTime = false;
- outputLiveData.setValue(currentValue);
- }
- }
- });
- return outputLiveData;
- }
-}
diff --git a/app/src/main/java/androidx/lifecycle/Transformations.kt b/app/src/main/java/androidx/lifecycle/Transformations.kt
new file mode 100644
index 0000000000..c3b9ed6894
--- /dev/null
+++ b/app/src/main/java/androidx/lifecycle/Transformations.kt
@@ -0,0 +1,196 @@
+/*
+ * 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.
+ */
+@file:JvmName("Transformations")
+
+package androidx.lifecycle
+
+import androidx.annotation.CheckResult
+import androidx.annotation.MainThread
+import androidx.arch.core.util.Function
+
+/**
+ * Returns a [LiveData] mapped from `this` LiveData by applying [transform] to each value set on
+ * `this` LiveData.
+ *
+ * This method is analogous to [io.reactivex.Observable.map].
+ *
+ * [transform] will be executed on the main thread.
+ *
+ * Here is an example mapping a simple `User` struct in a `LiveData` to a
+ * `LiveData` containing their full name as a `String`.
+ *
+ * ```
+ * val userLD : LiveData = ...;
+ * val userFullNameLD: LiveData = userLD.map { user -> user.firstName + user.lastName }
+ * ```
+ *
+ * @param transform a function to apply to each value set on `source` in order to set
+ * it on the output `LiveData`
+ * @return a LiveData mapped from `source` to type `` by applying
+ * `mapFunction` to each value set.
+ */
+@JvmName("map")
+@MainThread
+@CheckResult
+fun LiveData.map(
+ transform: (@JvmSuppressWildcards X) -> (@JvmSuppressWildcards Y)
+): LiveData {
+ val result = MediatorLiveData()
+ result.addSource(this) { x -> result.value = transform(x) }
+ return result
+}
+
+@Deprecated(
+ "Use kotlin functions, instead of outdated arch core Functions",
+ level = DeprecationLevel.HIDDEN
+)
+@JvmName("map")
+@MainThread
+@CheckResult
+fun LiveData.map(mapFunction: Function): LiveData {
+ val result = MediatorLiveData()
+ result.addSource(this) { x -> result.value = mapFunction.apply(x) }
+ return result
+}
+
+/**
+ * Returns a [LiveData] mapped from the input `this` `LiveData` by applying
+ * [transform] to each value set on `this`.
+ *
+ * The returned `LiveData` delegates to the most recent `LiveData` created by
+ * [transform] with the most recent value set to `this`, without
+ * changing the reference. In this way [transform] can change the 'backing'
+ * `LiveData` transparently to any observer registered to the `LiveData` returned
+ * by `switchMap()`.
+ *
+ * Note that when the backing `LiveData` is switched, no further values from the older
+ * `LiveData` will be set to the output `LiveData`. In this way, the method is
+ * analogous to [io.reactivex.Observable.switchMap].
+ *
+ * [transform] will be executed on the main thread.
+ *
+ * Here is an example class that holds a typed-in name of a user
+ * `String` (such as from an `EditText`) in a [MutableLiveData] and
+ * returns a `LiveData` containing a List of `User` objects for users that have
+ * that name. It populates that `LiveData` by requerying a repository-pattern object
+ * each time the typed name changes.
+ *
+ * This `ViewModel` would permit the observing UI to update "live" as the user ID text
+ * changes.
+ *
+ * ```
+ * class UserViewModel: AndroidViewModel {
+ * val nameQueryLiveData : MutableLiveData = ...
+ *
+ * fun usersWithNameLiveData(): LiveData> = nameQueryLiveData.switchMap {
+ * name -> myDataSource.usersWithNameLiveData(name)
+ * }
+ *
+ * fun setNameQuery(val name: String) {
+ * this.nameQueryLiveData.value = name;
+ * }
+ * }
+ * ```
+ *
+ * @param transform a function to apply to each value set on `source` to create a
+ * new delegate `LiveData` for the returned one
+ * @return a LiveData mapped from `source` to type `` by delegating to the LiveData
+ * returned by applying `switchMapFunction` to each value set
+ */
+@JvmName("switchMap")
+@MainThread
+@CheckResult
+fun LiveData.switchMap(
+ transform: (@JvmSuppressWildcards X) -> (@JvmSuppressWildcards LiveData)?
+): LiveData {
+ val result = MediatorLiveData()
+ result.addSource(this, object : Observer {
+ var liveData: LiveData? = null
+
+ override fun onChanged(value: X) {
+ val newLiveData = transform(value)
+ if (liveData === newLiveData) {
+ return
+ }
+ if (liveData != null) {
+ result.removeSource(liveData!!)
+ }
+ liveData = newLiveData
+ if (liveData != null) {
+ result.addSource(liveData!!) { y -> result.setValue(y) }
+ }
+ }
+ })
+ return result
+}
+
+@Deprecated(
+ "Use kotlin functions, instead of outdated arch core Functions",
+ level = DeprecationLevel.HIDDEN
+)
+@JvmName("switchMap")
+@MainThread
+@CheckResult
+fun LiveData.switchMap(switchMapFunction: Function>): LiveData {
+ val result = MediatorLiveData()
+ result.addSource(this, object : Observer {
+ var liveData: LiveData? = null
+
+ override fun onChanged(value: X) {
+ val newLiveData = switchMapFunction.apply(value)
+ if (liveData === newLiveData) {
+ return
+ }
+ if (liveData != null) {
+ result.removeSource(liveData!!)
+ }
+ liveData = newLiveData
+ if (liveData != null) {
+ result.addSource(liveData!!) { y -> result.setValue(y) }
+ }
+ }
+ })
+ return result
+}
+
+/**
+ * Creates a new [LiveData] object does not emit a value until the source `this` LiveData value
+ * has been changed. The value is considered changed if `equals()` yields `false`.
+ *
+ * @return a new [LiveData] of type `X`
+ */
+@JvmName("distinctUntilChanged")
+@MainThread
+@CheckResult
+fun LiveData.distinctUntilChanged(): LiveData {
+ val outputLiveData = MediatorLiveData()
+ var firstTime = true
+ if (isInitialized) {
+ outputLiveData.value = value
+ firstTime = false
+ }
+ outputLiveData.addSource(this) { value ->
+ val previousValue = outputLiveData.value
+ if (firstTime ||
+ previousValue == null && value != null ||
+ previousValue != null && previousValue != value
+ ) {
+ firstTime = false
+ outputLiveData.value = value
+ }
+ }
+ return outputLiveData
+}
\ No newline at end of file
diff --git a/patches/ComputableLiveData.patch b/patches/ComputableLiveData.patch
index 1ca6c324c4..8d56d192e9 100644
--- a/patches/ComputableLiveData.patch
+++ b/patches/ComputableLiveData.patch
@@ -1,29 +1,31 @@
---- /home/marcel/support/lifecycle/lifecycle-livedata/src/main/java/androidx/lifecycle/ComputableLiveData.java 2020-03-23 17:03:45.426122318 +0100
-+++ /home/marcel/email/app/src/main/java/androidx/lifecycle/ComputableLiveData.java 2020-06-14 11:48:36.977868184 +0200
-@@ -96,13 +96,26 @@ public abstract class ComputableLiveData
- // as long as it is invalid, keep computing.
- try {
- T value = null;
-+ boolean once = true;
-+ long last = android.os.SystemClock.elapsedRealtime();
- while (mInvalid.compareAndSet(true, false)) {
-+ long now = android.os.SystemClock.elapsedRealtime();
-+ if (value != null && (once || last + 2500 < now)) {
-+ eu.faircode.email.Log.i(mLiveData + " post once=" + once + " age=" + (now - last) + " ms");
-+ once = false;
-+ last = now;
-+ mLiveData.postValue(value);
-+ }
- computed = true;
- value = compute();
- }
- if (computed) {
- mLiveData.postValue(value);
- }
-+ } catch (Throwable ex) {
-+ // java.lang.IllegalStateException: Couldn't read row xxx column yyy
-+ eu.faircode.email.Log.e(ex);
-+ mInvalid.set(true);
- } finally {
- // release compute lock
- mComputing.set(false);
+diff --git a/app/src/main/java/androidx/lifecycle/ComputableLiveData.kt b/app/src/main/java/androidx/lifecycle/ComputableLiveData.kt
+index 94aa8d7f72..ebdb5de278 100644
+--- a/app/src/main/java/androidx/lifecycle/ComputableLiveData.kt
++++ b/app/src/main/java/androidx/lifecycle/ComputableLiveData.kt
+@@ -69,13 +69,26 @@ constructor(
+ // as long as it is invalid, keep computing.
+ try {
+ var value: T? = null
++ var once = true;
++ var last = android.os.SystemClock.elapsedRealtime()
+ while (invalid.compareAndSet(true, false)) {
++ var now = android.os.SystemClock.elapsedRealtime()
++ if (value != null && (once || last + 2500 < now)) {
++ eu.faircode.email.Log.i(liveData.toString() + " post once=" + once + " age=" + (now - last) + " ms")
++ once = false;
++ last = now;
++ liveData.postValue(value);
++ }
+ computed = true
+ value = compute()
+ }
+ if (computed) {
+ liveData.postValue(value)
+ }
++ } catch (ex: Throwable) {
++ // java.lang.IllegalStateException: Couldn't read row xxx column yyy
++ eu.faircode.email.Log.e(ex);
++ invalid.set(true);
+ } finally {
+ // release compute lock
+ computing.set(false)