Updated AndroidX lifecycle

This commit is contained in:
M66B 2023-03-09 07:43:39 +01:00
parent b94054dce2
commit 1ca1bbe42c
9 changed files with 408 additions and 408 deletions

View File

@ -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"

View File

@ -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.
* <p>
* 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)
* <p>
* This is an internal class for now, might be public if we see the necessity.
*
* @param <T> The type of the live data
* @hide internal
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
public abstract class ComputableLiveData<T> {
@SuppressWarnings("WeakerAccess") /* synthetic access */
final Executor mExecutor;
@SuppressWarnings("WeakerAccess") /* synthetic access */
final LiveData<T> 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<T>() {
@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<T> 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.
* <p>
* 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();
}

View File

@ -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 <T> The type of the live data
* @hide internal
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
abstract class ComputableLiveData<T> @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<T?> =
object : LiveData<T?>() {
override fun onActive() {
executor.execute(refreshRunnable)
}
}
/**
* The LiveData managed by this class.
*/
open val liveData: LiveData<T?> = _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
}

View File

@ -311,10 +311,11 @@ public abstract class LiveData<T> {
/**
* Returns the current value.
* <p>
* 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<T> {
return null;
}
/**
* Returns whether an explicit value has been set on this LiveData. If this returns
* <code>true</code>, then the current value can be retrieved from {@link #getValue()}.
* <p>
* Note that calling this method on a background thread may still result in this method
* returning <code>false</code> 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;
}

View File

@ -38,25 +38,25 @@ import java.util.Map;
* is called for either of them, we set a new value in {@code liveDataMerger}.
*
* <pre>
* LiveData<Integer> liveData1 = ...;
* LiveData<Integer> liveData2 = ...;
* LiveData&lt;Integer&gt; liveData1 = ...;
* LiveData&lt;Integer&gt; liveData2 = ...;
*
* MediatorLiveData<Integer> liveDataMerger = new MediatorLiveData<>();
* liveDataMerger.addSource(liveData1, value -> liveDataMerger.setValue(value));
* liveDataMerger.addSource(liveData2, value -> liveDataMerger.setValue(value));
* MediatorLiveData&lt;Integer&gt; liveDataMerger = new MediatorLiveData&lt;&gt;();
* liveDataMerger.addSource(liveData1, value -&gt; liveDataMerger.setValue(value));
* liveDataMerger.addSource(liveData2, value -&gt; liveDataMerger.setValue(value));
* </pre>
* <p>
* 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.
* <pre>
* liveDataMerger.addSource(liveData1, new Observer<Integer>() {
* liveDataMerger.addSource(liveData1, new Observer&lt;Integer&gt;() {
* private int count = 1;
*
* {@literal @}Override public void onChanged(@Nullable Integer s) {
* count++;
* liveDataMerger.setValue(s);
* if (count > 10) {
* if (count &gt; 10) {
* liveDataMerger.removeSource(liveData1);
* }
* }
@ -70,8 +70,24 @@ public class MediatorLiveData<T> extends MutableLiveData<T> {
private SafeIterableMap<LiveData<?>, 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.
* <p>
* {@code onChanged} callback will be called only when this {@code MediatorLiveData} is active.
* <p> If the given LiveData is already added as a source but with a different Observer,

View File

@ -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 <T> The type of the parameter
* A simple callback that can receive from [LiveData].
*
* @see LiveData LiveData - for a usage description.
*/
public interface Observer<T> {
*/
fun interface Observer<T> {
/**
* 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)
}

View File

@ -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}.
* <p>
* 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}.
* <p>
* This method is analogous to {@link io.reactivex.Observable#map}.
* <p>
* {@code transform} will be executed on the main thread.
* <p>
* 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}.
*
* <pre>
* LiveData&lt;User&gt; userLiveData = ...;
* LiveData&lt;String&gt; userFullNameLiveData =
* Transformations.map(
* userLiveData,
* user -> user.firstName + user.lastName);
* });
* </pre>
*
* @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 <X> the generic type parameter of {@code source}
* @param <Y> the generic type parameter of the returned {@code LiveData}
* @return a LiveData mapped from {@code source} to type {@code <Y>} by applying
* {@code mapFunction} to each value set.
*/
@MainThread
@NonNull
public static <X, Y> LiveData<Y> map(
@NonNull LiveData<X> source,
@NonNull final Function<X, Y> mapFunction) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(source, new Observer<X>() {
@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}.
* <p>
* 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()}.
* <p>
* 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}.
* <p>
* {@code switchMapFunction} will be executed on the main thread.
* <p>
* 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.
* <p>
* This {@code ViewModel} would permit the observing UI to update "live" as the user ID text
* changes.
*
* <pre>
* class UserViewModel extends AndroidViewModel {
* MutableLiveData&lt;String&gt; nameQueryLiveData = ...
*
* LiveData&lt;List&lt;String&gt;&gt; getUsersWithNameLiveData() {
* return Transformations.switchMap(
* nameQueryLiveData,
* name -> myDataSource.getUsersWithNameLiveData(name));
* }
*
* void setNameQuery(String name) {
* this.nameQueryLiveData.setValue(name);
* }
* }
* </pre>
*
* @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 <X> the generic type parameter of {@code source}
* @param <Y> the generic type parameter of the returned {@code LiveData}
* @return a LiveData mapped from {@code source} to type {@code <Y>} by delegating
* to the LiveData returned by applying {@code switchMapFunction} to each
* value set
*/
@MainThread
@NonNull
public static <X, Y> LiveData<Y> switchMap(
@NonNull LiveData<X> source,
@NonNull final Function<X, LiveData<Y>> switchMapFunction) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(source, new Observer<X>() {
LiveData<Y> mSource;
@Override
public void onChanged(@Nullable X x) {
LiveData<Y> newLiveData = switchMapFunction.apply(x);
if (mSource == newLiveData) {
return;
}
if (mSource != null) {
result.removeSource(mSource);
}
mSource = newLiveData;
if (mSource != null) {
result.addSource(mSource, new Observer<Y>() {
@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 <X> the generic type parameter of {@code source}
* @return a new {@link LiveData} of type {@code X}
*/
@MainThread
@NonNull
public static <X> LiveData<X> distinctUntilChanged(@NonNull LiveData<X> source) {
final MediatorLiveData<X> outputLiveData = new MediatorLiveData<>();
outputLiveData.addSource(source, new Observer<X>() {
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;
}
}

View File

@ -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<User> = ...;
* val userFullNameLD: LiveData<String> = 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 `<Y>` by applying
* `mapFunction` to each value set.
*/
@JvmName("map")
@MainThread
@CheckResult
fun <X, Y> LiveData<X>.map(
transform: (@JvmSuppressWildcards X) -> (@JvmSuppressWildcards Y)
): LiveData<Y> {
val result = MediatorLiveData<Y>()
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 <X, Y> LiveData<X>.map(mapFunction: Function<X, Y>): LiveData<Y> {
val result = MediatorLiveData<Y>()
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`.
* <p>
* 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.
* <p>
* This `ViewModel` would permit the observing UI to update "live" as the user ID text
* changes.
*
* ```
* class UserViewModel: AndroidViewModel {
* val nameQueryLiveData : MutableLiveData<String> = ...
*
* fun usersWithNameLiveData(): LiveData<List<String>> = 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 `<Y>` by delegating to the LiveData
* returned by applying `switchMapFunction` to each value set
*/
@JvmName("switchMap")
@MainThread
@CheckResult
fun <X, Y> LiveData<X>.switchMap(
transform: (@JvmSuppressWildcards X) -> (@JvmSuppressWildcards LiveData<Y>)?
): LiveData<Y> {
val result = MediatorLiveData<Y>()
result.addSource(this, object : Observer<X> {
var liveData: LiveData<Y>? = 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 <X, Y> LiveData<X>.switchMap(switchMapFunction: Function<X, LiveData<Y>>): LiveData<Y> {
val result = MediatorLiveData<Y>()
result.addSource(this, object : Observer<X> {
var liveData: LiveData<Y>? = 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 <X> LiveData<X>.distinctUntilChanged(): LiveData<X> {
val outputLiveData = MediatorLiveData<X>()
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
}

View File

@ -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)