mirror of https://github.com/M66B/FairEmail.git
parent
b94054dce2
commit
1ca1bbe42c
@ -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();
|
|
||||||
}
|
|
@ -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
|
||||||
|
}
|
@ -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<User> userLiveData = ...;
|
|
||||||
* LiveData<String> 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<String> nameQueryLiveData = ...
|
|
||||||
*
|
|
||||||
* LiveData<List<String>> 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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
||||||
|
}
|
@ -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
|
diff --git a/app/src/main/java/androidx/lifecycle/ComputableLiveData.kt b/app/src/main/java/androidx/lifecycle/ComputableLiveData.kt
|
||||||
+++ /home/marcel/email/app/src/main/java/androidx/lifecycle/ComputableLiveData.java 2020-06-14 11:48:36.977868184 +0200
|
index 94aa8d7f72..ebdb5de278 100644
|
||||||
@@ -96,13 +96,26 @@ public abstract class ComputableLiveData
|
--- a/app/src/main/java/androidx/lifecycle/ComputableLiveData.kt
|
||||||
// as long as it is invalid, keep computing.
|
+++ b/app/src/main/java/androidx/lifecycle/ComputableLiveData.kt
|
||||||
try {
|
@@ -69,13 +69,26 @@ constructor(
|
||||||
T value = null;
|
// as long as it is invalid, keep computing.
|
||||||
+ boolean once = true;
|
try {
|
||||||
+ long last = android.os.SystemClock.elapsedRealtime();
|
var value: T? = null
|
||||||
while (mInvalid.compareAndSet(true, false)) {
|
+ var once = true;
|
||||||
+ long now = android.os.SystemClock.elapsedRealtime();
|
+ var last = android.os.SystemClock.elapsedRealtime()
|
||||||
+ if (value != null && (once || last + 2500 < now)) {
|
while (invalid.compareAndSet(true, false)) {
|
||||||
+ eu.faircode.email.Log.i(mLiveData + " post once=" + once + " age=" + (now - last) + " ms");
|
+ var now = android.os.SystemClock.elapsedRealtime()
|
||||||
+ once = false;
|
+ if (value != null && (once || last + 2500 < now)) {
|
||||||
+ last = now;
|
+ eu.faircode.email.Log.i(liveData.toString() + " post once=" + once + " age=" + (now - last) + " ms")
|
||||||
+ mLiveData.postValue(value);
|
+ once = false;
|
||||||
+ }
|
+ last = now;
|
||||||
computed = true;
|
+ liveData.postValue(value);
|
||||||
value = compute();
|
+ }
|
||||||
}
|
computed = true
|
||||||
if (computed) {
|
value = compute()
|
||||||
mLiveData.postValue(value);
|
}
|
||||||
}
|
if (computed) {
|
||||||
+ } catch (Throwable ex) {
|
liveData.postValue(value)
|
||||||
+ // java.lang.IllegalStateException: Couldn't read row xxx column yyy
|
}
|
||||||
+ eu.faircode.email.Log.e(ex);
|
+ } catch (ex: Throwable) {
|
||||||
+ mInvalid.set(true);
|
+ // java.lang.IllegalStateException: Couldn't read row xxx column yyy
|
||||||
} finally {
|
+ eu.faircode.email.Log.e(ex);
|
||||||
// release compute lock
|
+ invalid.set(true);
|
||||||
mComputing.set(false);
|
} finally {
|
||||||
|
// release compute lock
|
||||||
|
computing.set(false)
|
||||||
|
Loading…
Reference in new issue