From 264bcedaeec8ac4504f5ae74406c598199709a69 Mon Sep 17 00:00:00 2001 From: M66B Date: Mon, 11 May 2026 10:02:23 +0200 Subject: [PATCH] Updated lifecycle to 2.10.0 --- app/build.gradle | 4 +- .../androidx/lifecycle/ComputableLiveData.kt | 33 ++-- .../androidx/lifecycle/CoroutineLiveData.kt | 167 +++++++++--------- .../java/androidx/lifecycle/FlowLiveData.kt | 119 ++++++------- .../java/androidx/lifecycle/LiveData.java | 19 +- .../main/java/androidx/lifecycle/LiveData.kt | 32 ++-- .../androidx/lifecycle/MediatorLiveData.java | 5 +- .../main/java/androidx/lifecycle/Observer.kt | 10 +- .../androidx/lifecycle/Transformations.kt | 150 ++++++++-------- patches/ComputableLiveData.patch | 40 ++++- 10 files changed, 300 insertions(+), 279 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 241935c0cf..81135c121e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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 diff --git a/app/src/main/java/androidx/lifecycle/ComputableLiveData.kt b/app/src/main/java/androidx/lifecycle/ComputableLiveData.kt index 0278c2cae0..79e893b93b 100644 --- a/app/src/main/java/androidx/lifecycle/ComputableLiveData.kt +++ b/app/src/main/java/androidx/lifecycle/ComputableLiveData.kt @@ -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 The type of the live data - * @hide internal -*/ + */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) -abstract class ComputableLiveData @JvmOverloads +public abstract class ComputableLiveData +@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 = object : LiveData() { override fun onActive() { executor.execute(refreshRunnable) } - } - /** - * The LiveData managed by this class. - */ - open val liveData: LiveData = _liveData + } + /** The LiveData managed by this class. */ + public open val liveData: LiveData = _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 -} \ No newline at end of file + @WorkerThread protected abstract fun compute(): T +} diff --git a/app/src/main/java/androidx/lifecycle/CoroutineLiveData.kt b/app/src/main/java/androidx/lifecycle/CoroutineLiveData.kt index 1cdc3036d8..9a08688a2f 100644 --- a/app/src/main/java/androidx/lifecycle/CoroutineLiveData.kt +++ b/app/src/main/java/androidx/lifecycle/CoroutineLiveData.kt @@ -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 { * 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 { * 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 { /** * 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( internal var target: CoroutineLiveData, - context: CoroutineContext + context: CoroutineContext, ) : LiveDataScope { override val latestValue: T? @@ -95,47 +95,41 @@ internal class LiveDataScopeImpl( } @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 MediatorLiveData.addDisposableSource( source: LiveData -): 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 = suspend LiveDataScope.() -> Unit -/** - * Handles running a block at most once to completion. - */ +/** Handles running a block at most once to completion. */ internal class BlockRunner( private val liveData: CoroutineLiveData, private val block: Block, 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( 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( 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( context: CoroutineContext = EmptyCoroutineContext, timeoutInMs: Long = DEFAULT_TIMEOUT, - block: Block + block: Block, ) : MediatorLiveData() { private var blockRunner: BlockRunner? 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( // 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): DisposableHandle { @@ -251,17 +244,17 @@ internal class CoroutineLiveData( * 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( * ``` * * @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 liveData( context: CoroutineContext = EmptyCoroutineContext, timeoutInMs: Long = DEFAULT_TIMEOUT, - block: suspend LiveDataScope.() -> Unit + block: suspend LiveDataScope.() -> Unit, ): LiveData = 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 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 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 liveData( public fun liveData( timeout: Duration, context: CoroutineContext = EmptyCoroutineContext, - block: suspend LiveDataScope.() -> Unit + block: suspend LiveDataScope.() -> Unit, ): LiveData = CoroutineLiveData(context, Api26Impl.toMillis(timeout), block) @RequiresApi(26) diff --git a/app/src/main/java/androidx/lifecycle/FlowLiveData.kt b/app/src/main/java/androidx/lifecycle/FlowLiveData.kt index 08036d026d..ac302272ac 100644 --- a/app/src/main/java/androidx/lifecycle/FlowLiveData.kt +++ b/app/src/main/java/androidx/lifecycle/FlowLiveData.kt @@ -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 Flow.asLiveData( context: CoroutineContext = EmptyCoroutineContext, - timeoutInMs: Long = DEFAULT_TIMEOUT -): LiveData = liveData(context, timeoutInMs) { - collect { - emit(it) - } -}.also { liveData -> - val flow = this - if (flow is StateFlow) { - if (ArchTaskExecutor.getInstance().isMainThread) { - liveData.value = flow.value - } else { - liveData.postValue(flow.value) + timeoutInMs: Long = DEFAULT_TIMEOUT, +): LiveData = + liveData(context, timeoutInMs) { collect { emit(it) } } + .also { liveData -> + val flow = this + if (flow is StateFlow) { + 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 Flow.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 LiveData.asFlow(): Flow = callbackFlow { - val observer = Observer { - 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 LiveData.asFlow(): Flow = + callbackFlow { + val observer = Observer { 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 LiveData.asFlow(): Flow = 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 Flow.asLiveData( timeout: Duration, - context: CoroutineContext = EmptyCoroutineContext + context: CoroutineContext = EmptyCoroutineContext, ): LiveData = asLiveData(context, Api26Impl.toMillis(timeout)) diff --git a/app/src/main/java/androidx/lifecycle/LiveData.java b/app/src/main/java/androidx/lifecycle/LiveData.java index 10b9fac733..0348892aa9 100644 --- a/app/src/main/java/androidx/lifecycle/LiveData.java +++ b/app/src/main/java/androidx/lifecycle/LiveData.java @@ -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 { * @param observer The Observer to receive events. */ @MainThread - public void removeObserver(@NonNull final Observer observer) { + public void removeObserver(final @NonNull Observer observer) { assertMainThread("removeObserver"); ObserverWrapper removed = mObservers.remove(observer); if (removed == null) { @@ -257,7 +258,7 @@ public abstract class LiveData { */ @SuppressWarnings("WeakerAccess") @MainThread - public void removeObservers(@NonNull final LifecycleOwner owner) { + public void removeObservers(final @NonNull LifecycleOwner owner) { assertMainThread("removeObservers"); for (Map.Entry, ObserverWrapper> entry : mObservers) { if (entry.getValue().isAttachedTo(owner)) { @@ -317,9 +318,8 @@ public abstract class LiveData { * * @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 { } class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver { - @NonNull - final LifecycleOwner mOwner; + final @NonNull LifecycleOwner mOwner; LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer observer) { super(observer); @@ -428,7 +427,7 @@ public abstract class LiveData { @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); diff --git a/app/src/main/java/androidx/lifecycle/LiveData.kt b/app/src/main/java/androidx/lifecycle/LiveData.kt index 951350ba62..eb4b2751f2 100644 --- a/app/src/main/java/androidx/lifecycle/LiveData.kt +++ b/app/src/main/java/androidx/lifecycle/LiveData.kt @@ -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 LiveData.observe( owner: LifecycleOwner, - crossinline onChanged: (T) -> Unit + crossinline onChanged: (T) -> Unit, ): Observer { val wrappedObserver = Observer { t -> onChanged.invoke(t) } observe(owner, wrappedObserver) diff --git a/app/src/main/java/androidx/lifecycle/MediatorLiveData.java b/app/src/main/java/androidx/lifecycle/MediatorLiveData.java index 0b352e958f..077b52a77e 100644 --- a/app/src/main/java/androidx/lifecycle/MediatorLiveData.java +++ b/app/src/main/java/androidx/lifecycle/MediatorLiveData.java @@ -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; /** diff --git a/app/src/main/java/androidx/lifecycle/Observer.kt b/app/src/main/java/androidx/lifecycle/Observer.kt index 3ce480f14d..a54003cbad 100644 --- a/app/src/main/java/androidx/lifecycle/Observer.kt +++ b/app/src/main/java/androidx/lifecycle/Observer.kt @@ -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 { + */ +public fun interface Observer { - /** - * 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) } diff --git a/app/src/main/java/androidx/lifecycle/Transformations.kt b/app/src/main/java/androidx/lifecycle/Transformations.kt index b1e55d1f6f..4c7d743c3f 100644 --- a/app/src/main/java/androidx/lifecycle/Transformations.kt +++ b/app/src/main/java/androidx/lifecycle/Transformations.kt @@ -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 = ...; * val userFullNameLD: LiveData = userLD.map { user -> user.firstName + user.lastName } * ``` * - * @param transform a function to apply to each value set on `source` in order to set - * it on the output `LiveData` - * @return a LiveData mapped from `source` to type `` by applying - * `mapFunction` to each value set. + * @param transform a function to apply to each value set on `source` in order to set it on the + * output `LiveData` + * @return a LiveData mapped from `source` to type `` by applying `mapFunction` to each value + * set. */ @JvmName("map") @MainThread @CheckResult @Suppress("UNCHECKED_CAST") -fun LiveData.map( +public fun LiveData.map( transform: (@JvmSuppressWildcards X) -> (@JvmSuppressWildcards Y) ): LiveData { - 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 LiveData.map(mapFunction: Function): LiveData { +public fun LiveData.map(mapFunction: Function): LiveData { val result = MediatorLiveData() result.addSource(this) { x -> result.value = mapFunction.apply(x) } return result } /** - * Returns a [LiveData] mapped from the input `this` `LiveData` by applying - * [transform] to each value set on `this`. + * Returns a [LiveData] mapped from the input `this` `LiveData` by applying [transform] to each + * value set on `this`. + * *

- * The returned `LiveData` delegates to the most recent `LiveData` created by - * [transform] with the most recent value set to `this`, without - * changing the reference. In this way [transform] can change the 'backing' - * `LiveData` transparently to any observer registered to the `LiveData` returned - * by `switchMap()`. + * 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. + * *

- * 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 LiveData.map(mapFunction: Function): LiveData { * } * ``` * - * @param transform a function to apply to each value set on `source` to create a - * new delegate `LiveData` for the returned one - * @return a LiveData mapped from `source` to type `` by delegating to the LiveData - * returned by applying `switchMapFunction` to each value set + * @param transform a function to apply to each value set on `source` to create a new delegate + * `LiveData` for the returned one + * @return a LiveData mapped from `source` to type `` by delegating to the LiveData returned by + * applying `switchMapFunction` to each value set */ @JvmName("switchMap") @MainThread @CheckResult @Suppress("UNCHECKED_CAST") -fun LiveData.switchMap( +public fun LiveData.switchMap( transform: (@JvmSuppressWildcards X) -> (@JvmSuppressWildcards LiveData)? ): LiveData { var liveData: LiveData? = null - val result = if (isInitialized) { - val initialLiveData = transform(value as X) - if (initialLiveData != null && initialLiveData.isInitialized) { - MediatorLiveData(initialLiveData.value) + val result = + if (isInitialized) { + val initialLiveData = transform(value as X) + if (initialLiveData != null && initialLiveData.isInitialized) { + MediatorLiveData(initialLiveData.value) + } else { + MediatorLiveData() + } } else { MediatorLiveData() } - } else { - MediatorLiveData() - } result.addSource(this) { value: X -> val newLiveData = transform(value) if (liveData !== newLiveData) { @@ -150,55 +151,60 @@ fun LiveData.switchMap( @Deprecated( "Use kotlin functions, instead of outdated arch core Functions", - level = DeprecationLevel.HIDDEN + level = DeprecationLevel.HIDDEN, ) @JvmName("switchMap") @MainThread @CheckResult -fun LiveData.switchMap(switchMapFunction: Function>): LiveData { +public fun LiveData.switchMap(switchMapFunction: Function>): LiveData { val result = MediatorLiveData() - result.addSource(this, object : Observer { - var liveData: LiveData? = null + result.addSource( + this, + object : Observer { + var liveData: LiveData? = null - override fun onChanged(value: X) { - val newLiveData = switchMapFunction.apply(value) - if (liveData === newLiveData) { - return - } - if (liveData != null) { - result.removeSource(liveData!!) + 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 LiveData.distinctUntilChanged(): LiveData { +public fun LiveData.distinctUntilChanged(): LiveData { var firstTime = true - val outputLiveData = if (isInitialized) { - firstTime = false - MediatorLiveData(value) - } else { - MediatorLiveData() - } + val outputLiveData = + if (isInitialized) { + firstTime = false + MediatorLiveData(value) + } else { + MediatorLiveData() + } 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 diff --git a/patches/ComputableLiveData.patch b/patches/ComputableLiveData.patch index 8d56d192e9..d801876071 100644 --- a/patches/ComputableLiveData.patch +++ b/patches/ComputableLiveData.patch @@ -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)