Updated lifecycle to 2.10.0

master
M66B 2 weeks ago
parent 5335e77509
commit 264bcedaee

@ -497,7 +497,7 @@ configurations.configureEach {
} else if (details.requested.group == "androidx.lifecycle" &&
details.requested.name != "lifecycle-extensions") {
//print("Pinning " + details.requested.group + ":" + details.requested.name + "\n")
details.useVersion "2.8.6"
details.useVersion "2.10.0"
} else if (details.requested.group == "org.apache.poi") {
//print("Pinning " + details.requested.group + ":" + details.requested.name + "\n")
details.useVersion "3.17"
@ -535,7 +535,7 @@ dependencies {
def lbm_version = "1.1.0"
def swiperefresh_version = "1.2.0-beta01" // 1.2.0
def documentfile_version = "1.1.0"
def lifecycle_version = "2.8.6" // 2.8.7/2.9.3/2.10.0/2.11.0-beta01
def lifecycle_version = "2.10.0" // 2.11.0-beta01
def lifecycle_extensions_version = "2.2.0"
def room_version = "2.4.3" // 2.5.2/2.6.1/2.7.2/2.8.4/3.0.0-alpha04
def sqlite_version = "2.5.1" // 2.5.2/2.6.2/2.7.0-alpha04

@ -25,36 +25,32 @@ 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)
* 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
public 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.
* 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()
) {
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
}
/** The LiveData managed by this class. */
public open val liveData: LiveData<T?> = _liveData
internal val invalid = AtomicBoolean(true)
internal val computing = AtomicBoolean(false)
@ -121,11 +117,10 @@ constructor(
*
* When there are active observers, this will trigger a call to [.compute].
*/
open fun invalidate() {
public open fun invalidate() {
ArchTaskExecutor.getInstance().executeOnMainThread(invalidationRunnable)
}
// TODO https://issuetracker.google.com/issues/112197238
@WorkerThread
protected abstract fun compute(): T
}
@WorkerThread protected abstract fun compute(): T
}

@ -14,6 +14,8 @@
* limitations under the License.
*/
@file:Suppress("FacadeClassJvmName") // Cannot be updated, the Kt name has been released
package androidx.lifecycle
import android.annotation.SuppressLint
@ -47,7 +49,6 @@ public interface LiveDataScope<T> {
* Note that this function suspends until the value is set on the [LiveData].
*
* @param value The new value for the [LiveData]
*
* @see emitSource
*/
public suspend fun emit(value: T)
@ -57,8 +58,7 @@ public interface LiveDataScope<T> {
* method will remove any source that was yielded before via [emitSource].
*
* @param source The [LiveData] instance whose values will be dispatched from the current
* [LiveData].
*
* [LiveData].
* @see emit
* @see MediatorLiveData.addSource
* @see MediatorLiveData.removeSource
@ -68,18 +68,18 @@ public interface LiveDataScope<T> {
/**
* References the current value of the [LiveData].
*
* If the block never `emit`ed a value, [latestValue] will be `null`. You can use this
* value to check what was then latest value `emit`ed by your `block` before it got cancelled.
* If the block never `emit`ed a value, [latestValue] will be `null`. You can use this value to
* check what was then latest value `emit`ed by your `block` before it got cancelled.
*
* Note that if the block called [emitSource], then `latestValue` will be last value
* dispatched by the `source` [LiveData].
* Note that if the block called [emitSource], then `latestValue` will be last value dispatched
* by the `source` [LiveData].
*/
public val latestValue: T?
}
internal class LiveDataScopeImpl<T>(
internal var target: CoroutineLiveData<T>,
context: CoroutineContext
context: CoroutineContext,
) : LiveDataScope<T> {
override val latestValue: T?
@ -95,47 +95,41 @@ internal class LiveDataScopeImpl<T>(
}
@SuppressLint("NullSafeMutableLiveData")
override suspend fun emit(value: T) = withContext(coroutineContext) {
target.clearSource()
target.value = value
}
override suspend fun emit(value: T) =
withContext(coroutineContext) {
target.clearSource()
target.value = value
}
}
internal suspend fun <T> MediatorLiveData<T>.addDisposableSource(
source: LiveData<T>
): EmittedSource = withContext(Dispatchers.Main.immediate) {
addSource(source) {
value = it
): EmittedSource =
withContext(Dispatchers.Main.immediate) {
addSource(source) { value = it }
EmittedSource(source = source, mediator = this@addDisposableSource)
}
EmittedSource(
source = source,
mediator = this@addDisposableSource
)
}
/**
* Holder class that keeps track of the previously dispatched [LiveData].
* It implements [DisposableHandle] interface while also providing a suspend clear function
* that we can use internally.
* Holder class that keeps track of the previously dispatched [LiveData]. It implements
* [DisposableHandle] interface while also providing a suspend clear function that we can use
* internally.
*/
internal class EmittedSource(
private val source: LiveData<*>,
private val mediator: MediatorLiveData<*>
private val mediator: MediatorLiveData<*>,
) : DisposableHandle {
// @MainThread
private var disposed = false
/**
* Unlike [dispose] which cannot be sync because it not a coroutine (and we do not want to
* lock), this version is a suspend function and does not return until source is removed.
*/
suspend fun disposeNow() = withContext(Dispatchers.Main.immediate) {
removeSource()
}
suspend fun disposeNow() = withContext(Dispatchers.Main.immediate) { removeSource() }
override fun dispose() {
CoroutineScope(Dispatchers.Main.immediate).launch {
removeSource()
}
CoroutineScope(Dispatchers.Main.immediate).launch { removeSource() }
}
@MainThread
@ -149,15 +143,13 @@ internal class EmittedSource(
internal typealias Block<T> = suspend LiveDataScope<T>.() -> Unit
/**
* Handles running a block at most once to completion.
*/
/** Handles running a block at most once to completion. */
internal class BlockRunner<T>(
private val liveData: CoroutineLiveData<T>,
private val block: Block<T>,
private val timeoutInMs: Long,
private val scope: CoroutineScope,
private val onDone: () -> Unit
private val onDone: () -> Unit,
) {
// currently running block job.
private var runningJob: Job? = null
@ -172,11 +164,12 @@ internal class BlockRunner<T>(
if (runningJob != null) {
return
}
runningJob = scope.launch {
val liveDataScope = LiveDataScopeImpl(liveData, coroutineContext)
block(liveDataScope)
onDone()
}
runningJob =
scope.launch {
val liveDataScope = LiveDataScopeImpl(liveData, coroutineContext)
block(liveDataScope)
onDone()
}
}
@MainThread
@ -184,29 +177,33 @@ internal class BlockRunner<T>(
if (cancellationJob != null) {
error("Cancel call cannot happen without a maybeRun")
}
cancellationJob = scope.launch(Dispatchers.Main.immediate) {
delay(timeoutInMs)
if (!liveData.hasActiveObservers()) {
// one last check on active observers to avoid any race condition between starting
// a running coroutine and cancelation
runningJob?.cancel()
runningJob = null
cancellationJob =
scope.launch(Dispatchers.Main.immediate) {
delay(timeoutInMs)
if (!liveData.hasActiveObservers()) {
// one last check on active observers to avoid any race condition between
// starting
// a running coroutine and cancelation
runningJob?.cancel()
runningJob = null
}
}
}
}
}
internal class CoroutineLiveData<T>(
context: CoroutineContext = EmptyCoroutineContext,
timeoutInMs: Long = DEFAULT_TIMEOUT,
block: Block<T>
block: Block<T>,
) : MediatorLiveData<T>() {
private var blockRunner: BlockRunner<T>?
private var emittedSource: EmittedSource? = null
init {
// use an intermediate supervisor job so that if we cancel individual block runs due to losing
// observers, it won't cancel the given context as we only cancel w/ the intention of possibly
// use an intermediate supervisor job so that if we cancel individual block runs due to
// losing
// observers, it won't cancel the given context as we only cancel w/ the intention of
// possibly
// relaunching using the same parent context.
val supervisorJob = SupervisorJob(context[Job])
@ -214,14 +211,10 @@ internal class CoroutineLiveData<T>(
// We default to Main dispatcher but developer can override it.
// The supervisor job is added last to isolate block runs.
val scope = CoroutineScope(Dispatchers.Main.immediate + context + supervisorJob)
blockRunner = BlockRunner(
liveData = this,
block = block,
timeoutInMs = timeoutInMs,
scope = scope
) {
blockRunner = null
}
blockRunner =
BlockRunner(liveData = this, block = block, timeoutInMs = timeoutInMs, scope = scope) {
blockRunner = null
}
}
internal suspend fun emitSource(source: LiveData<T>): DisposableHandle {
@ -251,17 +244,17 @@ internal class CoroutineLiveData<T>(
* Builds a LiveData that has values yielded from the given [block] that executes on a
* [LiveDataScope].
*
* The [block] starts executing when the returned [LiveData] becomes [active](LiveData.onActive).
* If the [LiveData] becomes [inactive](LiveData.onInactive) while the [block] is executing, it
* will be cancelled after [timeoutInMs] milliseconds unless the [LiveData] becomes active again
* before that timeout (to gracefully handle cases like Activity rotation). Any value
* [LiveDataScope.emit]ed from a cancelled [block] will be ignored.
* The [block] starts executing when the returned [LiveData] becomes [active](LiveData.onActive). If
* the [LiveData] becomes [inactive](LiveData.onInactive) while the [block] is executing, it will be
* cancelled after [timeoutInMs] milliseconds unless the [LiveData] becomes active again before that
* timeout (to gracefully handle cases like Activity rotation). Any value [LiveDataScope.emit]ed
* from a cancelled [block] will be ignored.
*
* After a cancellation, if the [LiveData] becomes active again, the [block] will be re-executed
* from the beginning. If you would like to continue the operations based on where it was stopped
* last, you can use the [LiveDataScope.latestValue] function to get the last
* [LiveDataScope.emit]ed value.
* last, you can use the [LiveDataScope.latestValue] function to get the last [LiveDataScope.emit]ed
* value.
*
* If the [block] completes successfully *or* is cancelled due to reasons other than [LiveData]
* becoming inactive, it *will not* be re-executed even after [LiveData] goes through active
* inactive cycle.
@ -345,34 +338,34 @@ internal class CoroutineLiveData<T>(
* ```
*
* @param context The CoroutineContext to run the given block in. Defaults to
* [EmptyCoroutineContext] combined with
* [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate]
* [EmptyCoroutineContext] combined with
* [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate]
* @param timeoutInMs The timeout in ms before cancelling the block if there are no active observers
* ([LiveData.hasActiveObservers]. Defaults to [DEFAULT_TIMEOUT].
* ([LiveData.hasActiveObservers]. Defaults to [DEFAULT_TIMEOUT].
* @param block The block to run when the [LiveData] has active observers.
*/
@JvmOverloads
public fun <T> liveData(
context: CoroutineContext = EmptyCoroutineContext,
timeoutInMs: Long = DEFAULT_TIMEOUT,
block: suspend LiveDataScope<T>.() -> Unit
block: suspend LiveDataScope<T>.() -> Unit,
): LiveData<T> = CoroutineLiveData(context, timeoutInMs, block)
/**
* Builds a LiveData that has values yielded from the given [block] that executes on a
* [LiveDataScope].
*
* The [block] starts executing when the returned [LiveData] becomes [active](LiveData.onActive).
* If the [LiveData] becomes [inactive](LiveData.onInactive) while the [block] is executing, it
* will be cancelled after the [timeout] duration unless the [LiveData] becomes active again
* before that timeout (to gracefully handle cases like Activity rotation). Any value
* [LiveDataScope.emit]ed from a cancelled [block] will be ignored.
* The [block] starts executing when the returned [LiveData] becomes [active](LiveData.onActive). If
* the [LiveData] becomes [inactive](LiveData.onInactive) while the [block] is executing, it will be
* cancelled after the [timeout] duration unless the [LiveData] becomes active again before that
* timeout (to gracefully handle cases like Activity rotation). Any value [LiveDataScope.emit]ed
* from a cancelled [block] will be ignored.
*
* After a cancellation, if the [LiveData] becomes active again, the [block] will be re-executed
* from the beginning. If you would like to continue the operations based on where it was stopped
* last, you can use the [LiveDataScope.latestValue] function to get the last
* [LiveDataScope.emit]ed value.
* last, you can use the [LiveDataScope.latestValue] function to get the last [LiveDataScope.emit]ed
* value.
*
* If the [block] completes successfully *or* is cancelled due to reasons other than [LiveData]
* becoming inactive, it *will not* be re-executed even after [LiveData] goes through active
* inactive cycle.
@ -381,11 +374,11 @@ public fun <T> liveData(
* coroutines documentation for details
* https://kotlinlang.org/docs/reference/coroutines/cancellation-and-timeouts.html.
*
* The [timeout] can be changed to fit different use cases better, for example increasing it
* will give more time to the [block] to complete even if [LiveData] is inactive. It is good for
* cases when [block] is finite (meaning it can complete successfully) and is costly to restart.
* Otherwise if a [block] is cheap to restart, decreasing the [timeout] value will allow to
* yield less values that aren't consumed by anything.
* The [timeout] can be changed to fit different use cases better, for example increasing it will
* give more time to the [block] to complete even if [LiveData] is inactive. It is good for cases
* when [block] is finite (meaning it can complete successfully) and is costly to restart. Otherwise
* if a [block] is cheap to restart, decreasing the [timeout] value will allow to yield less values
* that aren't consumed by anything.
*
* ```
* // a simple LiveData that receives value 3, 3 seconds after being observed for the first time.
@ -456,10 +449,10 @@ public fun <T> liveData(
* ```
*
* @param context The CoroutineContext to run the given block in. Defaults to
* [EmptyCoroutineContext] combined with
* [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate].
* [EmptyCoroutineContext] combined with
* [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate].
* @param timeout The timeout duration before cancelling the block if there are no active observers
* ([LiveData.hasActiveObservers].
* ([LiveData.hasActiveObservers].
* @param block The block to run when the [LiveData] has active observers.
*/
@RequiresApi(Build.VERSION_CODES.O)
@ -467,7 +460,7 @@ public fun <T> liveData(
public fun <T> liveData(
timeout: Duration,
context: CoroutineContext = EmptyCoroutineContext,
block: suspend LiveDataScope<T>.() -> Unit
block: suspend LiveDataScope<T>.() -> Unit,
): LiveData<T> = CoroutineLiveData(context, Api26Impl.toMillis(timeout), block)
@RequiresApi(26)

@ -36,14 +36,14 @@ import kotlinx.coroutines.withContext
/**
* Creates a LiveData that has values collected from the origin [Flow].
*
* If the origin [Flow] is a [StateFlow], then the initial value will be populated
* to the [LiveData]'s value field on the main thread.
* If the origin [Flow] is a [StateFlow], then the initial value will be populated to the
* [LiveData]'s value field on the main thread.
*
* The upstream flow collection starts when the returned [LiveData] becomes active
* ([LiveData.onActive]).
* If the [LiveData] becomes inactive ([LiveData.onInactive]) while the flow has not completed,
* the flow collection will be cancelled after [timeoutInMs] milliseconds unless the [LiveData]
* becomes active again before that timeout (to gracefully handle cases like Activity rotation).
* ([LiveData.onActive]). If the [LiveData] becomes inactive ([LiveData.onInactive]) while the flow
* has not completed, the flow collection will be cancelled after [timeoutInMs] milliseconds unless
* the [LiveData] becomes active again before that timeout (to gracefully handle cases like Activity
* rotation).
*
* After a cancellation, if the [LiveData] becomes active again, the upstream flow collection will
* be re-executed.
@ -54,44 +54,42 @@ import kotlinx.coroutines.withContext
*
* If flow completes with an exception, then exception will be delivered to the
* [CoroutineExceptionHandler][kotlinx.coroutines.CoroutineExceptionHandler] of provided [context].
* By default [EmptyCoroutineContext] is used to so an exception will be delivered to main's
* thread [UncaughtExceptionHandler][Thread.UncaughtExceptionHandler]. If your flow upstream is
* expected to throw, you can use [catch operator][kotlinx.coroutines.flow.catch] on upstream flow
* to emit a helpful error object.
* By default [EmptyCoroutineContext] is used to so an exception will be delivered to main's thread
* [UncaughtExceptionHandler][Thread.UncaughtExceptionHandler]. If your flow upstream is expected to
* throw, you can use [catch operator][kotlinx.coroutines.flow.catch] on upstream flow to emit a
* helpful error object.
*
* The [timeoutInMs] can be changed to fit different use cases better, for example increasing it
* will give more time to flow to complete before being canceled and is good for finite flows
* that are costly to restart. Otherwise if a flow is cheap to restart decreasing the [timeoutInMs]
* value will allow to produce less values that aren't consumed by anything.
* will give more time to flow to complete before being canceled and is good for finite flows that
* are costly to restart. Otherwise if a flow is cheap to restart decreasing the [timeoutInMs] value
* will allow to produce less values that aren't consumed by anything.
*
* @param context The CoroutineContext to collect the upstream flow in. Defaults to
* [EmptyCoroutineContext] combined with
* [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate]
* [EmptyCoroutineContext] combined with
* [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate]
* @param timeoutInMs The timeout in ms before cancelling the block if there are no active observers
* ([LiveData.hasActiveObservers]. Defaults to [DEFAULT_TIMEOUT].
* ([LiveData.hasActiveObservers]. Defaults to [DEFAULT_TIMEOUT].
*/
@JvmOverloads
public fun <T> Flow<T>.asLiveData(
context: CoroutineContext = EmptyCoroutineContext,
timeoutInMs: Long = DEFAULT_TIMEOUT
): LiveData<T> = liveData(context, timeoutInMs) {
collect {
emit(it)
}
}.also { liveData ->
val flow = this
if (flow is StateFlow<T>) {
if (ArchTaskExecutor.getInstance().isMainThread) {
liveData.value = flow.value
} else {
liveData.postValue(flow.value)
timeoutInMs: Long = DEFAULT_TIMEOUT,
): LiveData<T> =
liveData(context, timeoutInMs) { collect { emit(it) } }
.also { liveData ->
val flow = this
if (flow is StateFlow<T>) {
if (ArchTaskExecutor.getInstance().isMainThread) {
liveData.value = flow.value
} else {
liveData.postValue(flow.value)
}
}
}
}
}
/**
* Creates a [Flow] containing values dispatched by originating [LiveData]: at the start
* a flow collector receives the latest value held by LiveData and then observes LiveData updates.
* Creates a [Flow] containing values dispatched by originating [LiveData]: at the start a flow
* collector receives the latest value held by LiveData and then observes LiveData updates.
*
* When a collection of the returned flow starts the originating [LiveData] becomes
* [active][LiveData.onActive]. Similarly, when a collection completes [LiveData] becomes
@ -100,31 +98,26 @@ public fun <T> Flow<T>.asLiveData(
* BackPressure: the returned flow is conflated. There is no mechanism to suspend an emission by
* LiveData due to a slow collector, so collector always gets the most recent value emitted.
*/
public fun <T> LiveData<T>.asFlow(): Flow<T> = callbackFlow {
val observer = Observer<T> {
trySend(it)
}
withContext(Dispatchers.Main.immediate) {
observeForever(observer)
}
try {
withContext(Dispatchers.Main.immediate) { observeForever(observer) }
awaitCancellation()
} finally {
withContext(Dispatchers.Main.immediate + NonCancellable) {
removeObserver(observer)
public fun <T> LiveData<T>.asFlow(): Flow<T> =
callbackFlow {
val observer = Observer<T> { trySend(it) }
try {
withContext(Dispatchers.Main.immediate) { observeForever(observer) }
awaitCancellation()
} finally {
withContext(Dispatchers.Main.immediate + NonCancellable) {
removeObserver(observer)
}
}
}
}
}.conflate()
.conflate()
/**
* Creates a LiveData that has values collected from the origin [Flow].
*
* The upstream flow collection starts when the returned [LiveData] becomes active
* ([LiveData.onActive]).
* If the [LiveData] becomes inactive ([LiveData.onInactive]) while the flow has not completed,
* the flow collection will be cancelled after [timeout] unless the [LiveData]
* ([LiveData.onActive]). If the [LiveData] becomes inactive ([LiveData.onInactive]) while the flow
* has not completed, the flow collection will be cancelled after [timeout] unless the [LiveData]
* becomes active again before that timeout (to gracefully handle cases like Activity rotation).
*
* After a cancellation, if the [LiveData] becomes active again, the upstream flow collection will
@ -136,24 +129,24 @@ public fun <T> LiveData<T>.asFlow(): Flow<T> = callbackFlow {
*
* If flow completes with an exception, then exception will be delivered to the
* [CoroutineExceptionHandler][kotlinx.coroutines.CoroutineExceptionHandler] of provided [context].
* By default [EmptyCoroutineContext] is used to so an exception will be delivered to main's
* thread [UncaughtExceptionHandler][Thread.UncaughtExceptionHandler]. If your flow upstream is
* expected to throw, you can use [catch operator][kotlinx.coroutines.flow.catch] on upstream flow
* to emit a helpful error object.
* By default [EmptyCoroutineContext] is used to so an exception will be delivered to main's thread
* [UncaughtExceptionHandler][Thread.UncaughtExceptionHandler]. If your flow upstream is expected to
* throw, you can use [catch operator][kotlinx.coroutines.flow.catch] on upstream flow to emit a
* helpful error object.
*
* The [timeout] can be changed to fit different use cases better, for example increasing it
* will give more time to flow to complete before being canceled and is good for finite flows
* that are costly to restart. Otherwise if a flow is cheap to restart decreasing the [timeout]
* value will allow to produce less values that aren't consumed by anything.
* The [timeout] can be changed to fit different use cases better, for example increasing it will
* give more time to flow to complete before being canceled and is good for finite flows that are
* costly to restart. Otherwise if a flow is cheap to restart decreasing the [timeout] value will
* allow to produce less values that aren't consumed by anything.
*
* @param context The CoroutineContext to collect the upstream flow in. Defaults to
* [EmptyCoroutineContext] combined with
* [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate]
* [EmptyCoroutineContext] combined with
* [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate]
* @param timeout The timeout in ms before cancelling the block if there are no active observers
* ([LiveData.hasActiveObservers]. Defaults to [DEFAULT_TIMEOUT].
* ([LiveData.hasActiveObservers]. Defaults to [DEFAULT_TIMEOUT].
*/
@RequiresApi(Build.VERSION_CODES.O)
public fun <T> Flow<T>.asLiveData(
timeout: Duration,
context: CoroutineContext = EmptyCoroutineContext
context: CoroutineContext = EmptyCoroutineContext,
): LiveData<T> = asLiveData(context, Api26Impl.toMillis(timeout))

@ -20,11 +20,12 @@ 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 org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
import java.util.Iterator;
import java.util.Map;
@ -240,7 +241,7 @@ public abstract class LiveData<T> {
* @param observer The Observer to receive events.
*/
@MainThread
public void removeObserver(@NonNull final Observer<? super T> observer) {
public void removeObserver(final @NonNull Observer<? super T> observer) {
assertMainThread("removeObserver");
ObserverWrapper removed = mObservers.remove(observer);
if (removed == null) {
@ -257,7 +258,7 @@ public abstract class LiveData<T> {
*/
@SuppressWarnings("WeakerAccess")
@MainThread
public void removeObservers(@NonNull final LifecycleOwner owner) {
public void removeObservers(final @NonNull LifecycleOwner owner) {
assertMainThread("removeObservers");
for (Map.Entry<Observer<? super T>, ObserverWrapper> entry : mObservers) {
if (entry.getValue().isAttachedTo(owner)) {
@ -317,9 +318,8 @@ public abstract class LiveData<T> {
*
* @return the current value or null if {@link #isInitialized()} is false
*/
@SuppressWarnings("unchecked")
@Nullable
public T getValue() {
@SuppressWarnings({"unchecked", "GetterSetterNullability"})
public @Nullable T getValue() {
Object data = mData;
if (data != NOT_SET) {
return (T) data;
@ -413,8 +413,7 @@ public abstract class LiveData<T> {
}
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
@NonNull
final LifecycleOwner mOwner;
final @NonNull LifecycleOwner mOwner;
LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {
super(observer);
@ -428,7 +427,7 @@ public abstract class LiveData<T> {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
Lifecycle.@NonNull Event event) {
Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
if (currentState == DESTROYED) {
removeObserver(mObserver);

@ -14,31 +14,31 @@
* limitations under the License.
*/
@file:Suppress("FacadeClassJvmName") // Cannot be updated, the Kt name has been released
package androidx.lifecycle
import androidx.annotation.MainThread
/**
* Adds the given [onChanged] lambda as an observer within the lifespan of the given
* [owner] and returns a reference to observer.
* The events are dispatched on the main thread. If LiveData already has data
* set, it will be delivered to the onChanged.
* Adds the given [onChanged] lambda as an observer within the lifespan of the given [owner] and
* returns a reference to observer. The events are dispatched on the main thread. If LiveData
* already has data set, it will be delivered to the onChanged.
*
* The observer will only receive events if the owner is in [Lifecycle.State.STARTED]
* or [Lifecycle.State.RESUMED] state (active).
* The observer will only receive events if the owner is in [Lifecycle.State.STARTED] or
* [Lifecycle.State.RESUMED] state (active).
*
* If the owner moves to the [Lifecycle.State.DESTROYED] state, the observer will
* automatically be removed.
* If the owner moves to the [Lifecycle.State.DESTROYED] state, the observer will automatically be
* removed.
*
* When data changes while the [owner] is not active, it will not receive any updates.
* If it becomes active again, it will receive the last available data automatically.
* When data changes while the [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 and the owner.
* 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 and the owner.
*
* If the given owner is already in [Lifecycle.State.DESTROYED] state, LiveData
* ignores the call.
* If the given owner is already in [Lifecycle.State.DESTROYED] state, LiveData ignores the call.
*/
@Deprecated(
"This extension method is not required when using Kotlin 1.4. " +
@ -47,7 +47,7 @@ import androidx.annotation.MainThread
@MainThread
public inline fun <T> LiveData<T>.observe(
owner: LifecycleOwner,
crossinline onChanged: (T) -> Unit
crossinline onChanged: (T) -> Unit,
): Observer<T> {
val wrappedObserver = Observer<T> { t -> onChanged.invoke(t) }
observe(owner, wrappedObserver)

@ -18,10 +18,11 @@ 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 org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
import java.util.Map;
/**

@ -19,11 +19,9 @@ package androidx.lifecycle
* A simple callback that can receive from [LiveData].
*
* @see LiveData LiveData - for a usage description.
*/
fun interface Observer<T> {
*/
public fun interface Observer<T> {
/**
* Called when the data is changed to [value].
*/
fun onChanged(value: T)
/** Called when the data is changed to [value]. */
public fun onChanged(value: T)
}

@ -29,72 +29,72 @@ import androidx.arch.core.util.Function
*
* [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`.
* 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.
* @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
@Suppress("UNCHECKED_CAST")
fun <X, Y> LiveData<X>.map(
public fun <X, Y> LiveData<X>.map(
transform: (@JvmSuppressWildcards X) -> (@JvmSuppressWildcards Y)
): LiveData<Y> {
val result = if (isInitialized) {
MediatorLiveData(transform(value as X))
} else {
MediatorLiveData()
}
val result =
if (isInitialized) {
MediatorLiveData(transform(value as X))
} else {
MediatorLiveData()
}
result.addSource(this) { x -> result.value = transform(x) }
return result
}
@Deprecated(
"Use kotlin functions, instead of outdated arch core Functions",
level = DeprecationLevel.HIDDEN
level = DeprecationLevel.HIDDEN,
)
@JvmName("map")
@MainThread
@CheckResult
fun <X, Y> LiveData<X>.map(mapFunction: Function<X, Y>): LiveData<Y> {
public 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`.
* 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()`.
* 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].
* 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.
* 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.
* This `ViewModel` would permit the observing UI to update "live" as the user ID text changes.
*
* ```
* class UserViewModel: AndroidViewModel {
@ -110,29 +110,30 @@ fun <X, Y> LiveData<X>.map(mapFunction: Function<X, Y>): LiveData<Y> {
* }
* ```
*
* @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
* @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
@Suppress("UNCHECKED_CAST")
fun <X, Y> LiveData<X>.switchMap(
public fun <X, Y> LiveData<X>.switchMap(
transform: (@JvmSuppressWildcards X) -> (@JvmSuppressWildcards LiveData<Y>)?
): LiveData<Y> {
var liveData: LiveData<Y>? = null
val result = if (isInitialized) {
val initialLiveData = transform(value as X)
if (initialLiveData != null && initialLiveData.isInitialized) {
MediatorLiveData<Y>(initialLiveData.value)
val result =
if (isInitialized) {
val initialLiveData = transform(value as X)
if (initialLiveData != null && initialLiveData.isInitialized) {
MediatorLiveData<Y>(initialLiveData.value)
} else {
MediatorLiveData<Y>()
}
} else {
MediatorLiveData<Y>()
}
} else {
MediatorLiveData<Y>()
}
result.addSource(this) { value: X ->
val newLiveData = transform(value)
if (liveData !== newLiveData) {
@ -150,55 +151,60 @@ fun <X, Y> LiveData<X>.switchMap(
@Deprecated(
"Use kotlin functions, instead of outdated arch core Functions",
level = DeprecationLevel.HIDDEN
level = DeprecationLevel.HIDDEN,
)
@JvmName("switchMap")
@MainThread
@CheckResult
fun <X, Y> LiveData<X>.switchMap(switchMapFunction: Function<X, LiveData<Y>>): LiveData<Y> {
public 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
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!!)
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) }
}
}
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`.
* 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> {
public fun <X> LiveData<X>.distinctUntilChanged(): LiveData<X> {
var firstTime = true
val outputLiveData = if (isInitialized) {
firstTime = false
MediatorLiveData<X>(value)
} else {
MediatorLiveData<X>()
}
val outputLiveData =
if (isInitialized) {
firstTime = false
MediatorLiveData<X>(value)
} else {
MediatorLiveData<X>()
}
outputLiveData.addSource(this) { value ->
val previousValue = outputLiveData.value
if (firstTime ||
previousValue == null && value != null ||
previousValue != null && previousValue != value
if (
firstTime ||
previousValue == null && value != null ||
previousValue != null && previousValue != value
) {
firstTime = false
outputLiveData.value = value

@ -1,8 +1,8 @@
diff --git a/app/src/main/java/androidx/lifecycle/ComputableLiveData.kt b/app/src/main/java/androidx/lifecycle/ComputableLiveData.kt
index 94aa8d7f72..ebdb5de278 100644
index 397e28a23f..79e893b93b 100644
--- a/app/src/main/java/androidx/lifecycle/ComputableLiveData.kt
+++ b/app/src/main/java/androidx/lifecycle/ComputableLiveData.kt
@@ -69,13 +69,26 @@ constructor(
@@ -65,13 +65,26 @@ constructor(internal val executor: Executor = ArchTaskExecutor.getIOThreadExecut
// as long as it is invalid, keep computing.
try {
var value: T? = null
@ -29,3 +29,39 @@ index 94aa8d7f72..ebdb5de278 100644
} finally {
// release compute lock
computing.set(false)
diff --git a/patches/ComputableLiveData.patch b/patches/ComputableLiveData.patch
index 8d56d192e9..e69de29bb2 100644
--- a/patches/ComputableLiveData.patch
+++ b/patches/ComputableLiveData.patch
@@ -1,31 +0,0 @@
-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)

Loading…
Cancel
Save