Updated lifecycle to 2.10.0

master
M66B 4 months ago
parent 5335e77509
commit 264bcedaee

@ -497,7 +497,7 @@ configurations.configureEach {
} else if (details.requested.group == "androidx.lifecycle" && } else if (details.requested.group == "androidx.lifecycle" &&
details.requested.name != "lifecycle-extensions") { details.requested.name != "lifecycle-extensions") {
//print("Pinning " + details.requested.group + ":" + details.requested.name + "\n") //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") { } else if (details.requested.group == "org.apache.poi") {
//print("Pinning " + details.requested.group + ":" + details.requested.name + "\n") //print("Pinning " + details.requested.group + ":" + details.requested.name + "\n")
details.useVersion "3.17" details.useVersion "3.17"
@ -535,7 +535,7 @@ dependencies {
def lbm_version = "1.1.0" def lbm_version = "1.1.0"
def swiperefresh_version = "1.2.0-beta01" // 1.2.0 def swiperefresh_version = "1.2.0-beta01" // 1.2.0
def documentfile_version = "1.1.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 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 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 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. * 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 * It can be invalidated via [invalidate], which will result in a call to [compute] if there are
* [compute] if there are active observers (or when they start observing) * active observers (or when they start observing)
* *
* This is an internal class for now, might be public if we see the necessity. * This is an internal class for now, might be public if we see the necessity.
* *
* @param <T> The type of the live data * @param <T> The type of the live data
* @hide internal */
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @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 * Creates a computable live data that computes values on the specified executor or the arch IO
* or the arch IO thread executor by default. * thread executor by default.
* *
* @param executor Executor that is used to compute new LiveData values. * @param executor Executor that is used to compute new LiveData values.
*/ */
constructor( constructor(internal val executor: Executor = ArchTaskExecutor.getIOThreadExecutor()) {
internal val executor: Executor = ArchTaskExecutor.getIOThreadExecutor()
) {
private val _liveData: LiveData<T?> = private val _liveData: LiveData<T?> =
object : LiveData<T?>() { object : LiveData<T?>() {
override fun onActive() { override fun onActive() {
executor.execute(refreshRunnable) executor.execute(refreshRunnable)
} }
} }
/** /** The LiveData managed by this class. */
* The LiveData managed by this class. public open val liveData: LiveData<T?> = _liveData
*/
open val liveData: LiveData<T?> = _liveData
internal val invalid = AtomicBoolean(true) internal val invalid = AtomicBoolean(true)
internal val computing = AtomicBoolean(false) internal val computing = AtomicBoolean(false)
@ -121,11 +117,10 @@ constructor(
* *
* When there are active observers, this will trigger a call to [.compute]. * When there are active observers, this will trigger a call to [.compute].
*/ */
open fun invalidate() { public open fun invalidate() {
ArchTaskExecutor.getInstance().executeOnMainThread(invalidationRunnable) ArchTaskExecutor.getInstance().executeOnMainThread(invalidationRunnable)
} }
// TODO https://issuetracker.google.com/issues/112197238 // TODO https://issuetracker.google.com/issues/112197238
@WorkerThread @WorkerThread protected abstract fun compute(): T
protected abstract fun compute(): T }
}

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

@ -36,14 +36,14 @@ import kotlinx.coroutines.withContext
/** /**
* Creates a LiveData that has values collected from the origin [Flow]. * 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 * If the origin [Flow] is a [StateFlow], then the initial value will be populated to the
* to the [LiveData]'s value field on the main thread. * [LiveData]'s value field on the main thread.
* *
* The upstream flow collection starts when the returned [LiveData] becomes active * The upstream flow collection starts when the returned [LiveData] becomes active
* ([LiveData.onActive]). * ([LiveData.onActive]). If the [LiveData] becomes inactive ([LiveData.onInactive]) while the flow
* If the [LiveData] becomes inactive ([LiveData.onInactive]) while the flow has not completed, * has not completed, the flow collection will be cancelled after [timeoutInMs] milliseconds unless
* the flow collection will be cancelled after [timeoutInMs] milliseconds unless the [LiveData] * the [LiveData] becomes active again before that timeout (to gracefully handle cases like Activity
* becomes active again before that timeout (to gracefully handle cases like Activity rotation). * rotation).
* *
* After a cancellation, if the [LiveData] becomes active again, the upstream flow collection will * After a cancellation, if the [LiveData] becomes active again, the upstream flow collection will
* be re-executed. * be re-executed.
@ -54,44 +54,42 @@ import kotlinx.coroutines.withContext
* *
* If flow completes with an exception, then exception will be delivered to the * If flow completes with an exception, then exception will be delivered to the
* [CoroutineExceptionHandler][kotlinx.coroutines.CoroutineExceptionHandler] of provided [context]. * [CoroutineExceptionHandler][kotlinx.coroutines.CoroutineExceptionHandler] of provided [context].
* By default [EmptyCoroutineContext] is used to so an exception will be delivered to main's * By default [EmptyCoroutineContext] is used to so an exception will be delivered to main's thread
* thread [UncaughtExceptionHandler][Thread.UncaughtExceptionHandler]. If your flow upstream is * [UncaughtExceptionHandler][Thread.UncaughtExceptionHandler]. If your flow upstream is expected to
* expected to throw, you can use [catch operator][kotlinx.coroutines.flow.catch] on upstream flow * throw, you can use [catch operator][kotlinx.coroutines.flow.catch] on upstream flow to emit a
* to emit a helpful error object. * helpful error object.
* *
* The [timeoutInMs] can be changed to fit different use cases better, for example increasing it * 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 * will give more time to flow to complete before being canceled and is good for finite flows that
* that are costly to restart. Otherwise if a flow is cheap to restart decreasing the [timeoutInMs] * are costly to restart. Otherwise if a flow is cheap to restart decreasing the [timeoutInMs] value
* value will allow to produce less values that aren't consumed by anything. * will allow to produce less values that aren't consumed by anything.
* *
* @param context The CoroutineContext to collect the upstream flow in. Defaults to * @param context The CoroutineContext to collect the upstream flow in. Defaults to
* [EmptyCoroutineContext] combined with * [EmptyCoroutineContext] combined with
* [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate] * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate]
* @param timeoutInMs The timeout in ms before cancelling the block if there are no active observers * @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 @JvmOverloads
public fun <T> Flow<T>.asLiveData( public fun <T> Flow<T>.asLiveData(
context: CoroutineContext = EmptyCoroutineContext, context: CoroutineContext = EmptyCoroutineContext,
timeoutInMs: Long = DEFAULT_TIMEOUT timeoutInMs: Long = DEFAULT_TIMEOUT,
): LiveData<T> = liveData(context, timeoutInMs) { ): LiveData<T> =
collect { liveData(context, timeoutInMs) { collect { emit(it) } }
emit(it) .also { liveData ->
} val flow = this
}.also { liveData -> if (flow is StateFlow<T>) {
val flow = this if (ArchTaskExecutor.getInstance().isMainThread) {
if (flow is StateFlow<T>) { liveData.value = flow.value
if (ArchTaskExecutor.getInstance().isMainThread) { } else {
liveData.value = flow.value liveData.postValue(flow.value)
} else { }
liveData.postValue(flow.value) }
} }
}
}
/** /**
* Creates a [Flow] containing values dispatched by originating [LiveData]: at the start * Creates a [Flow] containing values dispatched by originating [LiveData]: at the start a flow
* a flow collector receives the latest value held by LiveData and then observes LiveData updates. * 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 * When a collection of the returned flow starts the originating [LiveData] becomes
* [active][LiveData.onActive]. Similarly, when a collection completes [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 * 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. * LiveData due to a slow collector, so collector always gets the most recent value emitted.
*/ */
public fun <T> LiveData<T>.asFlow(): Flow<T> = callbackFlow { public fun <T> LiveData<T>.asFlow(): Flow<T> =
val observer = Observer<T> { callbackFlow {
trySend(it) val observer = Observer<T> { trySend(it) }
} try {
withContext(Dispatchers.Main.immediate) { withContext(Dispatchers.Main.immediate) { observeForever(observer) }
observeForever(observer) awaitCancellation()
} } finally {
withContext(Dispatchers.Main.immediate + NonCancellable) {
try { removeObserver(observer)
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]. * Creates a LiveData that has values collected from the origin [Flow].
* *
* The upstream flow collection starts when the returned [LiveData] becomes active * The upstream flow collection starts when the returned [LiveData] becomes active
* ([LiveData.onActive]). * ([LiveData.onActive]). If the [LiveData] becomes inactive ([LiveData.onInactive]) while the flow
* If the [LiveData] becomes inactive ([LiveData.onInactive]) while the flow has not completed, * has not completed, the flow collection will be cancelled after [timeout] unless the [LiveData]
* the flow collection will be cancelled after [timeout] unless the [LiveData]
* becomes active again before that timeout (to gracefully handle cases like Activity rotation). * 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 * 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 * If flow completes with an exception, then exception will be delivered to the
* [CoroutineExceptionHandler][kotlinx.coroutines.CoroutineExceptionHandler] of provided [context]. * [CoroutineExceptionHandler][kotlinx.coroutines.CoroutineExceptionHandler] of provided [context].
* By default [EmptyCoroutineContext] is used to so an exception will be delivered to main's * By default [EmptyCoroutineContext] is used to so an exception will be delivered to main's thread
* thread [UncaughtExceptionHandler][Thread.UncaughtExceptionHandler]. If your flow upstream is * [UncaughtExceptionHandler][Thread.UncaughtExceptionHandler]. If your flow upstream is expected to
* expected to throw, you can use [catch operator][kotlinx.coroutines.flow.catch] on upstream flow * throw, you can use [catch operator][kotlinx.coroutines.flow.catch] on upstream flow to emit a
* to emit a helpful error object. * helpful error object.
* *
* The [timeout] can be changed to fit different use cases better, for example increasing it * The [timeout] can be changed to fit different use cases better, for example increasing it will
* will give more time to flow to complete before being canceled and is good for finite flows * give more time to flow to complete before being canceled and is good for finite flows that are
* that are costly to restart. Otherwise if a flow is cheap to restart decreasing the [timeout] * costly to restart. Otherwise if a flow is cheap to restart decreasing the [timeout] value will
* value will allow to produce less values that aren't consumed by anything. * allow to produce less values that aren't consumed by anything.
* *
* @param context The CoroutineContext to collect the upstream flow in. Defaults to * @param context The CoroutineContext to collect the upstream flow in. Defaults to
* [EmptyCoroutineContext] combined with * [EmptyCoroutineContext] combined with
* [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate] * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate]
* @param timeout The timeout in ms before cancelling the block if there are no active observers * @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) @RequiresApi(Build.VERSION_CODES.O)
public fun <T> Flow<T>.asLiveData( public fun <T> Flow<T>.asLiveData(
timeout: Duration, timeout: Duration,
context: CoroutineContext = EmptyCoroutineContext context: CoroutineContext = EmptyCoroutineContext,
): LiveData<T> = asLiveData(context, Api26Impl.toMillis(timeout)) ): 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 static androidx.lifecycle.Lifecycle.State.STARTED;
import androidx.annotation.MainThread; import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.arch.core.executor.ArchTaskExecutor; import androidx.arch.core.executor.ArchTaskExecutor;
import androidx.arch.core.internal.SafeIterableMap; import androidx.arch.core.internal.SafeIterableMap;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
@ -240,7 +241,7 @@ public abstract class LiveData<T> {
* @param observer The Observer to receive events. * @param observer The Observer to receive events.
*/ */
@MainThread @MainThread
public void removeObserver(@NonNull final Observer<? super T> observer) { public void removeObserver(final @NonNull Observer<? super T> observer) {
assertMainThread("removeObserver"); assertMainThread("removeObserver");
ObserverWrapper removed = mObservers.remove(observer); ObserverWrapper removed = mObservers.remove(observer);
if (removed == null) { if (removed == null) {
@ -257,7 +258,7 @@ public abstract class LiveData<T> {
*/ */
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
@MainThread @MainThread
public void removeObservers(@NonNull final LifecycleOwner owner) { public void removeObservers(final @NonNull LifecycleOwner owner) {
assertMainThread("removeObservers"); assertMainThread("removeObservers");
for (Map.Entry<Observer<? super T>, ObserverWrapper> entry : mObservers) { for (Map.Entry<Observer<? super T>, ObserverWrapper> entry : mObservers) {
if (entry.getValue().isAttachedTo(owner)) { 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 * @return the current value or null if {@link #isInitialized()} is false
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings({"unchecked", "GetterSetterNullability"})
@Nullable public @Nullable T getValue() {
public T getValue() {
Object data = mData; Object data = mData;
if (data != NOT_SET) { if (data != NOT_SET) {
return (T) data; return (T) data;
@ -413,8 +413,7 @@ public abstract class LiveData<T> {
} }
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver { class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
@NonNull final @NonNull LifecycleOwner mOwner;
final LifecycleOwner mOwner;
LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) { LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {
super(observer); super(observer);
@ -428,7 +427,7 @@ public abstract class LiveData<T> {
@Override @Override
public void onStateChanged(@NonNull LifecycleOwner source, public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) { Lifecycle.@NonNull Event event) {
Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState(); Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
if (currentState == DESTROYED) { if (currentState == DESTROYED) {
removeObserver(mObserver); removeObserver(mObserver);

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

@ -18,10 +18,11 @@ package androidx.lifecycle;
import androidx.annotation.CallSuper; import androidx.annotation.CallSuper;
import androidx.annotation.MainThread; import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.arch.core.internal.SafeIterableMap; import androidx.arch.core.internal.SafeIterableMap;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
import java.util.Map; import java.util.Map;
/** /**

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

@ -29,72 +29,72 @@ import androidx.arch.core.util.Function
* *
* [transform] will be executed on the main thread. * [transform] will be executed on the main thread.
* *
* Here is an example mapping a simple `User` struct in a `LiveData` to a * Here is an example mapping a simple `User` struct in a `LiveData` to a `LiveData` containing
* `LiveData` containing their full name as a `String`. * their full name as a `String`.
* *
* ``` * ```
* val userLD : LiveData<User> = ...; * val userLD : LiveData<User> = ...;
* val userFullNameLD: LiveData<String> = userLD.map { user -> user.firstName + user.lastName } * 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 * @param transform a function to apply to each value set on `source` in order to set it on the
* it on the output `LiveData` * output `LiveData`
* @return a LiveData mapped from `source` to type `<Y>` by applying * @return a LiveData mapped from `source` to type `<Y>` by applying `mapFunction` to each value
* `mapFunction` to each value set. * set.
*/ */
@JvmName("map") @JvmName("map")
@MainThread @MainThread
@CheckResult @CheckResult
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
fun <X, Y> LiveData<X>.map( public fun <X, Y> LiveData<X>.map(
transform: (@JvmSuppressWildcards X) -> (@JvmSuppressWildcards Y) transform: (@JvmSuppressWildcards X) -> (@JvmSuppressWildcards Y)
): LiveData<Y> { ): LiveData<Y> {
val result = if (isInitialized) { val result =
MediatorLiveData(transform(value as X)) if (isInitialized) {
} else { MediatorLiveData(transform(value as X))
MediatorLiveData() } else {
} MediatorLiveData()
}
result.addSource(this) { x -> result.value = transform(x) } result.addSource(this) { x -> result.value = transform(x) }
return result return result
} }
@Deprecated( @Deprecated(
"Use kotlin functions, instead of outdated arch core Functions", "Use kotlin functions, instead of outdated arch core Functions",
level = DeprecationLevel.HIDDEN level = DeprecationLevel.HIDDEN,
) )
@JvmName("map") @JvmName("map")
@MainThread @MainThread
@CheckResult @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>() val result = MediatorLiveData<Y>()
result.addSource(this) { x -> result.value = mapFunction.apply(x) } result.addSource(this) { x -> result.value = mapFunction.apply(x) }
return result return result
} }
/** /**
* Returns a [LiveData] mapped from the input `this` `LiveData` by applying * Returns a [LiveData] mapped from the input `this` `LiveData` by applying [transform] to each
* [transform] to each value set on `this`. * value set on `this`.
*
* <p> * <p>
* The returned `LiveData` delegates to the most recent `LiveData` created by * The returned `LiveData` delegates to the most recent `LiveData` created by [transform] with the
* [transform] with the most recent value set to `this`, without * most recent value set to `this`, without changing the reference. In this way [transform] can
* changing the reference. In this way [transform] can change the 'backing' * change the 'backing' `LiveData` transparently to any observer registered to the `LiveData`
* `LiveData` transparently to any observer registered to the `LiveData` returned * returned by `switchMap()`.
* by `switchMap()`.
* *
* Note that when the backing `LiveData` is switched, no further values from the older * Note that when the backing `LiveData` is switched, no further values from the older `LiveData`
* `LiveData` will be set to the output `LiveData`. In this way, the method is * will be set to the output `LiveData`. In this way, the method is analogous to
* analogous to [io.reactivex.Observable.switchMap]. * [io.reactivex.Observable.switchMap].
* *
* [transform] will be executed on the main thread. * [transform] will be executed on the main thread.
* *
* Here is an example class that holds a typed-in name of a user * Here is an example class that holds a typed-in name of a user `String` (such as from an
* `String` (such as from an `EditText`) in a [MutableLiveData] and * `EditText`) in a [MutableLiveData] and returns a `LiveData` containing a List of `User` objects
* returns a `LiveData` containing a List of `User` objects for users that have * for users that have that name. It populates that `LiveData` by requerying a repository-pattern
* that name. It populates that `LiveData` by requerying a repository-pattern object * object each time the typed name changes.
* each time the typed name changes. *
* <p> * <p>
* This `ViewModel` would permit the observing UI to update "live" as the user ID text * This `ViewModel` would permit the observing UI to update "live" as the user ID text changes.
* changes.
* *
* ``` * ```
* class UserViewModel: AndroidViewModel { * 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 * @param transform a function to apply to each value set on `source` to create a new delegate
* new delegate `LiveData` for the returned one * `LiveData` for the returned one
* @return a LiveData mapped from `source` to type `<Y>` by delegating to the LiveData * @return a LiveData mapped from `source` to type `<Y>` by delegating to the LiveData returned by
* returned by applying `switchMapFunction` to each value set * applying `switchMapFunction` to each value set
*/ */
@JvmName("switchMap") @JvmName("switchMap")
@MainThread @MainThread
@CheckResult @CheckResult
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
fun <X, Y> LiveData<X>.switchMap( public fun <X, Y> LiveData<X>.switchMap(
transform: (@JvmSuppressWildcards X) -> (@JvmSuppressWildcards LiveData<Y>)? transform: (@JvmSuppressWildcards X) -> (@JvmSuppressWildcards LiveData<Y>)?
): LiveData<Y> { ): LiveData<Y> {
var liveData: LiveData<Y>? = null var liveData: LiveData<Y>? = null
val result = if (isInitialized) { val result =
val initialLiveData = transform(value as X) if (isInitialized) {
if (initialLiveData != null && initialLiveData.isInitialized) { val initialLiveData = transform(value as X)
MediatorLiveData<Y>(initialLiveData.value) if (initialLiveData != null && initialLiveData.isInitialized) {
MediatorLiveData<Y>(initialLiveData.value)
} else {
MediatorLiveData<Y>()
}
} else { } else {
MediatorLiveData<Y>() MediatorLiveData<Y>()
} }
} else {
MediatorLiveData<Y>()
}
result.addSource(this) { value: X -> result.addSource(this) { value: X ->
val newLiveData = transform(value) val newLiveData = transform(value)
if (liveData !== newLiveData) { if (liveData !== newLiveData) {
@ -150,55 +151,60 @@ fun <X, Y> LiveData<X>.switchMap(
@Deprecated( @Deprecated(
"Use kotlin functions, instead of outdated arch core Functions", "Use kotlin functions, instead of outdated arch core Functions",
level = DeprecationLevel.HIDDEN level = DeprecationLevel.HIDDEN,
) )
@JvmName("switchMap") @JvmName("switchMap")
@MainThread @MainThread
@CheckResult @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>() val result = MediatorLiveData<Y>()
result.addSource(this, object : Observer<X> { result.addSource(
var liveData: LiveData<Y>? = null this,
object : Observer<X> {
var liveData: LiveData<Y>? = null
override fun onChanged(value: X) { override fun onChanged(value: X) {
val newLiveData = switchMapFunction.apply(value) val newLiveData = switchMapFunction.apply(value)
if (liveData === newLiveData) { if (liveData === newLiveData) {
return return
} }
if (liveData != null) { if (liveData != null) {
result.removeSource(liveData!!) 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 return result
} }
/** /**
* Creates a new [LiveData] object does not emit a value until the source `this` LiveData value * Creates a new [LiveData] object does not emit a value until the source `this` LiveData value has
* has been changed. The value is considered changed if `equals()` yields `false`. * been changed. The value is considered changed if `equals()` yields `false`.
* *
* @return a new [LiveData] of type `X` * @return a new [LiveData] of type `X`
*/ */
@JvmName("distinctUntilChanged") @JvmName("distinctUntilChanged")
@MainThread @MainThread
@CheckResult @CheckResult
fun <X> LiveData<X>.distinctUntilChanged(): LiveData<X> { public fun <X> LiveData<X>.distinctUntilChanged(): LiveData<X> {
var firstTime = true var firstTime = true
val outputLiveData = if (isInitialized) { val outputLiveData =
firstTime = false if (isInitialized) {
MediatorLiveData<X>(value) firstTime = false
} else { MediatorLiveData<X>(value)
MediatorLiveData<X>() } else {
} MediatorLiveData<X>()
}
outputLiveData.addSource(this) { value -> outputLiveData.addSource(this) { value ->
val previousValue = outputLiveData.value val previousValue = outputLiveData.value
if (firstTime || if (
previousValue == null && value != null || firstTime ||
previousValue != null && previousValue != value previousValue == null && value != null ||
previousValue != null && previousValue != value
) { ) {
firstTime = false firstTime = false
outputLiveData.value = value 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 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 --- a/app/src/main/java/androidx/lifecycle/ComputableLiveData.kt
+++ b/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. // as long as it is invalid, keep computing.
try { try {
var value: T? = null var value: T? = null
@ -29,3 +29,39 @@ index 94aa8d7f72..ebdb5de278 100644
} finally { } finally {
// release compute lock // release compute lock
computing.set(false) 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