/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.lifecycle.compose
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
/**
* Schedule an effect to run when the [Lifecycle] receives a specific [Lifecycle.Event].
*
* Using a [LifecycleEventObserver] to listen for when [LifecycleEventEffect] enters
* the composition, [onEvent] will be launched when receiving the specified [event].
*
* This function should **not** be used to listen for [Lifecycle.Event.ON_DESTROY] because
* Compose stops recomposing after receiving a [Lifecycle.Event.ON_STOP] and will never be
* aware of an ON_DESTROY to launch [onEvent].
*
* This function should also **not** be used to launch tasks in response to callback
* events by way of storing callback data as a [Lifecycle.State] in a [MutableState].
* Instead, see [currentStateAsState] to obtain a [State<Lifecycle.State>][State]
* that may be used to launch jobs in response to state changes.
*
* @sample androidx.lifecycle.compose.samples.lifecycleEventEffectSample
*
* @param event The [Lifecycle.Event] to listen for
* @param lifecycleOwner The lifecycle owner to attach an observer
* @param onEvent The effect to be launched when we receive an [event] callback
*
* @throws IllegalArgumentException if attempting to listen for [Lifecycle.Event.ON_DESTROY]
*/
@Composable
fun LifecycleEventEffect(
event: Lifecycle.Event,
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
onEvent: () -> Unit
) {
if (event == Lifecycle.Event.ON_DESTROY) {
throw IllegalArgumentException("LifecycleEventEffect cannot be used to " +
"listen for Lifecycle.Event.ON_DESTROY, since Compose disposes of the " +
"composition before ON_DESTROY observers are invoked.")
}
// Safely update the current `onEvent` lambda when a new one is provided
val currentOnEvent by rememberUpdatedState(onEvent)
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, e ->
if (e == event) {
currentOnEvent()
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
}
/**
* Schedule a pair of effects to run when the [Lifecycle] receives either a
* [Lifecycle.Event.ON_START] or [Lifecycle.Event.ON_STOP] (or any new unique
* value of [key1]). The ON_START effect will be the body of the [effects]
* block and the ON_STOP effect will be within the
* (onStopOrDispose clause)[LifecycleStartStopEffectScope.onStopOrDispose]:
*
* ```
* LifecycleStartEffect(lifecycleOwner) {
* // add ON_START effect here
*
* onStopOrDispose {
* // add clean up for work kicked off in the ON_START effect here
* }
* }
* ```
*
* @sample androidx.lifecycle.compose.samples.lifecycleStartEffectSample
*
* A [LifecycleStartEffect] **must** include an
* [onStopOrDispose][LifecycleStartStopEffectScope.onStopOrDispose] clause as the final
* statement in its [effects] block. If your operation does not require an effect for
* _both_ [Lifecycle.Event.ON_START] and [Lifecycle.Event.ON_STOP], a [LifecycleEventEffect]
* should be used instead.
*
* A [LifecycleStartEffect]'s _key_ is a value that defines the identity of the effect.
* If the key changes, the [LifecycleStartEffect] must
* [dispose][LifecycleStartStopEffectScope.onStopOrDispose] its current [effects] and
* reset by calling [effects] again. Examples of keys include:
*
* * Observable objects that the effect subscribes to
* * Unique request parameters to an operation that must cancel and retry if those parameters change
*
* This function uses a [LifecycleEventObserver] to listen for when [LifecycleStartEffect]
* enters the composition and the effects will be launched when receiving a
* [Lifecycle.Event.ON_START] or [Lifecycle.Event.ON_STOP] event, respectively. If the
* [LifecycleStartEffect] leaves the composition prior to receiving an [Lifecycle.Event.ON_STOP]
* event, [onStopOrDispose][LifecycleStartStopEffectScope.onStopOrDispose] will be called to
* clean up the work that was kicked off in the ON_START effect.
*
* This function should **not** be used to launch tasks in response to callback
* events by way of storing callback data as a [Lifecycle.State] in a [MutableState].
* Instead, see [currentStateAsState] to obtain a [State<Lifecycle.State>][State]
* that may be used to launch jobs in response to state changes.
*
* @param key1 The unique value to trigger recomposition upon change
* @param lifecycleOwner The lifecycle owner to attach an observer
* @param effects The effects to be launched when we receive the respective event callbacks
*/
@Composable
fun LifecycleStartEffect(
key1: Any?,
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
effects: LifecycleStartStopEffectScope.() -> LifecycleStopOrDisposeEffectResult
) {
val lifecycleStartStopEffectScope = remember(key1, lifecycleOwner) {
LifecycleStartStopEffectScope(lifecycleOwner.lifecycle)
}
LifecycleStartEffectImpl(lifecycleOwner, lifecycleStartStopEffectScope, effects)
}
/**
* Schedule a pair of effects to run when the [Lifecycle] receives either a
* [Lifecycle.Event.ON_START] or [Lifecycle.Event.ON_STOP] (or any new unique
* value of [key1] or [key2]). The ON_START effect will be the body of the
* [effects] block and the ON_STOP effect will be within the
* (onStopOrDispose clause)[LifecycleStartStopEffectScope.onStopOrDispose]:
*
* ```
* LifecycleStartEffect(lifecycleOwner) {
* // add ON_START effect here
*
* onStopOrDispose {
* // add clean up for work kicked off in the ON_START effect here
* }
* }
* ```
*
* @sample androidx.lifecycle.compose.samples.lifecycleStartEffectSample
*
* A [LifecycleStartEffect] **must** include an
* [onStopOrDispose][LifecycleStartStopEffectScope.onStopOrDispose] clause as the final
* statement in its [effects] block. If your operation does not require an effect for
* _both_ [Lifecycle.Event.ON_START] and [Lifecycle.Event.ON_STOP], a [LifecycleEventEffect]
* should be used instead.
*
* A [LifecycleStartEffect]'s _key_ is a value that defines the identity of the effect.
* If a key changes, the [LifecycleStartEffect] must
* [dispose][LifecycleStartStopEffectScope.onStopOrDispose] its current [effects] and
* reset by calling [effects] again. Examples of keys include:
*
* * Observable objects that the effect subscribes to
* * Unique request parameters to an operation that must cancel and retry if those parameters change
*
* This function uses a [LifecycleEventObserver] to listen for when [LifecycleStartEffect]
* enters the composition and the effects will be launched when receiving a
* [Lifecycle.Event.ON_START] or [Lifecycle.Event.ON_STOP] event, respectively. If the
* [LifecycleStartEffect] leaves the composition prior to receiving an [Lifecycle.Event.ON_STOP]
* event, [onStopOrDispose][LifecycleStartStopEffectScope.onStopOrDispose] will be called to
* clean up the work that was kicked off in the ON_START effect.
*
* This function should **not** be used to launch tasks in response to callback
* events by way of storing callback data as a [Lifecycle.State] in a [MutableState].
* Instead, see [currentStateAsState] to obtain a [State<Lifecycle.State>][State]
* that may be used to launch jobs in response to state changes.
*
* @param key1 A unique value to trigger recomposition upon change
* @param key2 A unique value to trigger recomposition upon change
* @param lifecycleOwner The lifecycle owner to attach an observer
* @param effects The effects to be launched when we receive the respective event callbacks
*/
@Composable
fun LifecycleStartEffect(
key1: Any?,
key2: Any?,
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
effects: LifecycleStartStopEffectScope.() -> LifecycleStopOrDisposeEffectResult
) {
val lifecycleStartStopEffectScope = remember(key1, key2, lifecycleOwner) {
LifecycleStartStopEffectScope(lifecycleOwner.lifecycle)
}
LifecycleStartEffectImpl(lifecycleOwner, lifecycleStartStopEffectScope, effects)
}
/**
* Schedule a pair of effects to run when the [Lifecycle] receives either a
* [Lifecycle.Event.ON_START] or [Lifecycle.Event.ON_STOP] (or any new unique
* value of [key1] or [key2] or [key3]). The ON_START effect will be the body
* of the [effects] block and the ON_STOP effect will be within the
* (onStopOrDispose clause)[LifecycleStartStopEffectScope.onStopOrDispose]:
*
* ```
* LifecycleStartEffect(lifecycleOwner) {
* // add ON_START effect here
*
* onStopOrDispose {
* // add clean up for work kicked off in the ON_START effect here
* }
* }
* ```
*
* @sample androidx.lifecycle.compose.samples.lifecycleStartEffectSample
*
* A [LifecycleStartEffect] **must** include an
* [onStopOrDispose][LifecycleStartStopEffectScope.onStopOrDispose] clause as the final
* statement in its [effects] block. If your operation does not require an effect for
* _both_ [Lifecycle.Event.ON_START] and [Lifecycle.Event.ON_STOP], a [LifecycleEventEffect]
* should be used instead.
*
* A [LifecycleStartEffect]'s _key_ is a value that defines the identity of the effect.
* If a key changes, the [LifecycleStartEffect] must
* [dispose][LifecycleStartStopEffectScope.onStopOrDispose] its current [effects] and
* reset by calling [effects] again. Examples of keys include:
*
* * Observable objects that the effect subscribes to
* * Unique request parameters to an operation that must cancel and retry if those parameters change
*
* This function uses a [LifecycleEventObserver] to listen for when [LifecycleStartEffect]
* enters the composition and the effects will be launched when receiving a
* [Lifecycle.Event.ON_START] or [Lifecycle.Event.ON_STOP] event, respectively. If the
* [LifecycleStartEffect] leaves the composition prior to receiving an [Lifecycle.Event.ON_STOP]
* event, [onStopOrDispose][LifecycleStartStopEffectScope.onStopOrDispose] will be called to
* clean up the work that was kicked off in the ON_START effect.
*
* This function should **not** be used to launch tasks in response to callback
* events by way of storing callback data as a [Lifecycle.State] in a [MutableState].
* Instead, see [currentStateAsState] to obtain a [State<Lifecycle.State>][State]
* that may be used to launch jobs in response to state changes.
*
* @param key1 The unique value to trigger recomposition upon change
* @param key2 The unique value to trigger recomposition upon change
* @param key3 The unique value to trigger recomposition upon change
* @param lifecycleOwner The lifecycle owner to attach an observer
* @param effects The effects to be launched when we receive the respective event callbacks
*/
@Composable
fun LifecycleStartEffect(
key1: Any?,
key2: Any?,
key3: Any?,
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
effects: LifecycleStartStopEffectScope.() -> LifecycleStopOrDisposeEffectResult
) {
val lifecycleStartStopEffectScope = remember(key1, key2, key3, lifecycleOwner) {
LifecycleStartStopEffectScope(lifecycleOwner.lifecycle)
}
LifecycleStartEffectImpl(lifecycleOwner, lifecycleStartStopEffectScope, effects)
}
/**
* Schedule a pair of effects to run when the [Lifecycle] receives either a
* [Lifecycle.Event.ON_START] or [Lifecycle.Event.ON_STOP] (or any new unique
* value of [keys]). The ON_START effect will be the body of the [effects]
* block and the ON_STOP effect will be within the
* (onStopOrDispose clause)[LifecycleStartStopEffectScope.onStopOrDispose]:
*
* ```
* LifecycleStartEffect(lifecycleOwner) {
* // add ON_START effect here
*
* onStopOrDispose {
* // add clean up for work kicked off in the ON_START effect here
* }
* }
* ```
*
* @sample androidx.lifecycle.compose.samples.lifecycleStartEffectSample
*
* A [LifecycleStartEffect] **must** include an
* [onStopOrDispose][LifecycleStartStopEffectScope.onStopOrDispose] clause as the final
* statement in its [effects] block. If your operation does not require an effect for
* _both_ [Lifecycle.Event.ON_START] and [Lifecycle.Event.ON_STOP], a [LifecycleEventEffect]
* should be used instead.
*
* A [LifecycleStartEffect]'s _key_ is a value that defines the identity of the effect.
* If a key changes, the [LifecycleStartEffect] must
* [dispose][LifecycleStartStopEffectScope.onStopOrDispose] its current [effects] and
* reset by calling [effects] again. Examples of keys include:
*
* * Observable objects that the effect subscribes to
* * Unique request parameters to an operation that must cancel and retry if those parameters change
*
* This function uses a [LifecycleEventObserver] to listen for when [LifecycleStartEffect]
* enters the composition and the effects will be launched when receiving a
* [Lifecycle.Event.ON_START] or [Lifecycle.Event.ON_STOP] event, respectively. If the
* [LifecycleStartEffect] leaves the composition prior to receiving an [Lifecycle.Event.ON_STOP]
* event, [onStopOrDispose][LifecycleStartStopEffectScope.onStopOrDispose] will be called to
* clean up the work that was kicked off in the ON_START effect.
*
* This function should **not** be used to launch tasks in response to callback
* events by way of storing callback data as a [Lifecycle.State] in a [MutableState].
* Instead, see [currentStateAsState] to obtain a [State<Lifecycle.State>][State]
* that may be used to launch jobs in response to state changes.
*
* @param keys The unique values to trigger recomposition upon changes
* @param lifecycleOwner The lifecycle owner to attach an observer
* @param effects The effects to be launched when we receive the respective event callbacks
*/
@Composable
fun LifecycleStartEffect(
vararg keys: Any?,
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
effects: LifecycleStartStopEffectScope.() -> LifecycleStopOrDisposeEffectResult
) {
val lifecycleStartStopEffectScope = remember(*keys, lifecycleOwner) {
LifecycleStartStopEffectScope(lifecycleOwner.lifecycle)
}
LifecycleStartEffectImpl(lifecycleOwner, lifecycleStartStopEffectScope, effects)
}
@Composable
private fun LifecycleStartEffectImpl(
lifecycleOwner: LifecycleOwner,
scope: LifecycleStartStopEffectScope,
effects: LifecycleStartStopEffectScope.() -> LifecycleStopOrDisposeEffectResult
) {
DisposableEffect(lifecycleOwner, scope) {
var effectResult: LifecycleStopOrDisposeEffectResult? = null
val observer = LifecycleEventObserver { _, event ->
when (event) {
Lifecycle.Event.ON_START -> with(scope) {
effectResult = effects()
}
Lifecycle.Event.ON_STOP -> effectResult?.runStopOrDisposeEffect()
else -> {}
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
effectResult?.runStopOrDisposeEffect()
}
}
}
/**
* Interface used for [LifecycleStartEffect] to run the effect within the onStopOrDispose
* clause when an (ON_STOP)[Lifecycle.Event.ON_STOP] event is received or when cleanup is
* needed for the work that was kicked off in the ON_START effect.
*/
interface LifecycleStopOrDisposeEffectResult {
fun runStopOrDisposeEffect()
}
/**
* Receiver scope for [LifecycleStartEffect] that offers the [onStopOrDispose] clause to
* couple the ON_START effect. This should be the last statement in any call to
* [LifecycleStartEffect].
*
* This scope is also a [LifecycleOwner] to allow access to the
* (lifecycle)[LifecycleStartStopEffectScope.lifecycle] within the [onStopOrDispose] clause.
*
* @param lifecycle The lifecycle being observed by this receiver scope
*/
class LifecycleStartStopEffectScope(override val lifecycle: Lifecycle) : LifecycleOwner {
/**
* Provide the [onStopOrDisposeEffect] to the [LifecycleStartEffect] to run when the
* observer receives an (ON_STOP)[Lifecycle.Event.ON_STOP] event or must undergo cleanup.
*/
inline fun onStopOrDispose(
crossinline onStopOrDisposeEffect: LifecycleOwner.() -> Unit
): LifecycleStopOrDisposeEffectResult = object : LifecycleStopOrDisposeEffectResult {
override fun runStopOrDisposeEffect() {
onStopOrDisposeEffect()
}
}
}
/**
* Schedule a pair of effects to run when the [Lifecycle] receives either a
* [Lifecycle.Event.ON_RESUME] or [Lifecycle.Event.ON_PAUSE] (or any new unique
* value of [key1]). The ON_RESUME effect will be the body of the [effects]
* block and the ON_PAUSE effect will be within the
* (onPauseOrDispose clause)[LifecycleResumePauseEffectScope.onPauseOrDispose]:
*
* ```
* LifecycleResumeEffect(lifecycleOwner) {
* // add ON_RESUME effect here
*
* onPauseOrDispose {
* // add clean up for work kicked off in the ON_RESUME effect here
* }
* }
* ```
*
* @sample androidx.lifecycle.compose.samples.lifecycleResumeEffectSample
*
* A [LifecycleResumeEffect] **must** include an
* [onPauseOrDispose][LifecycleResumePauseEffectScope.onPauseOrDispose] clause as
* the final statement in its [effects] block. If your operation does not require
* an effect for _both_ [Lifecycle.Event.ON_RESUME] and [Lifecycle.Event.ON_PAUSE],
* a [LifecycleEventEffect] should be used instead.
*
* A [LifecycleResumeEffect]'s _key_ is a value that defines the identity of the effect.
* If a key changes, the [LifecycleResumeEffect] must
* [dispose][LifecycleResumePauseEffectScope.onPauseOrDispose] its current [effects] and
* reset by calling [effects] again. Examples of keys include:
*
* * Observable objects that the effect subscribes to
* * Unique request parameters to an operation that must cancel and retry if those parameters change
*
* This function uses a [LifecycleEventObserver] to listen for when [LifecycleResumeEffect]
* enters the composition and the effects will be launched when receiving a
* [Lifecycle.Event.ON_RESUME] or [Lifecycle.Event.ON_PAUSE] event, respectively. If the
* [LifecycleResumeEffect] leaves the composition prior to receiving an [Lifecycle.Event.ON_PAUSE]
* event, [onPauseOrDispose][LifecycleResumePauseEffectScope.onPauseOrDispose] will be called
* to clean up the work that was kicked off in the ON_RESUME effect.
*
* This function should **not** be used to launch tasks in response to callback
* events by way of storing callback data as a [Lifecycle.State] in a [MutableState].
* Instead, see [currentStateAsState] to obtain a [State<Lifecycle.State>][State]
* that may be used to launch jobs in response to state changes.
*
* @param key1 The unique value to trigger recomposition upon change
* @param lifecycleOwner The lifecycle owner to attach an observer
* @param effects The effects to be launched when we receive the respective event callbacks
*/
@Composable
fun LifecycleResumeEffect(
key1: Any?,
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
effects: LifecycleResumePauseEffectScope.() -> LifecyclePauseOrDisposeEffectResult
) {
val lifecycleResumePauseEffectScope = remember(key1, lifecycleOwner) {
LifecycleResumePauseEffectScope(lifecycleOwner.lifecycle)
}
LifecycleResumeEffectImpl(lifecycleOwner, lifecycleResumePauseEffectScope, effects)
}
/**
* Schedule a pair of effects to run when the [Lifecycle] receives either a
* [Lifecycle.Event.ON_RESUME] or [Lifecycle.Event.ON_PAUSE] (or any new unique
* value of [key1] or [key2]). The ON_RESUME effect will be the body of the
* [effects] block and the ON_PAUSE effect will be within the
* (onPauseOrDispose clause)[LifecycleResumePauseEffectScope.onPauseOrDispose]:
*
* ```
* LifecycleResumeEffect(lifecycleOwner) {
* // add ON_RESUME effect here
*
* onPauseOrDispose {
* // add clean up for work kicked off in the ON_RESUME effect here
* }
* }
* ```
*
* @sample androidx.lifecycle.compose.samples.lifecycleResumeEffectSample
*
* A [LifecycleResumeEffect] **must** include an
* [onPauseOrDispose][LifecycleResumePauseEffectScope.onPauseOrDispose] clause as
* the final statement in its [effects] block. If your operation does not require
* an effect for _both_ [Lifecycle.Event.ON_RESUME] and [Lifecycle.Event.ON_PAUSE],
* a [LifecycleEventEffect] should be used instead.
*
* A [LifecycleResumeEffect]'s _key_ is a value that defines the identity of the effect.
* If a key changes, the [LifecycleResumeEffect] must
* [dispose][LifecycleResumePauseEffectScope.onPauseOrDispose] its current [effects] and
* reset by calling [effects] again. Examples of keys include:
*
* * Observable objects that the effect subscribes to
* * Unique request parameters to an operation that must cancel and retry if those parameters change
*
* This function uses a [LifecycleEventObserver] to listen for when [LifecycleResumeEffect]
* enters the composition and the effects will be launched when receiving a
* [Lifecycle.Event.ON_RESUME] or [Lifecycle.Event.ON_PAUSE] event, respectively. If the
* [LifecycleResumeEffect] leaves the composition prior to receiving an [Lifecycle.Event.ON_PAUSE]
* event, [onPauseOrDispose][LifecycleResumePauseEffectScope.onPauseOrDispose] will be called
* to clean up the work that was kicked off in the ON_RESUME effect.
*
* This function should **not** be used to launch tasks in response to callback
* events by way of storing callback data as a [Lifecycle.State] in a [MutableState].
* Instead, see [currentStateAsState] to obtain a [State<Lifecycle.State>][State]
* that may be used to launch jobs in response to state changes.
*
* @param key1 A unique value to trigger recomposition upon change
* @param key2 A unique value to trigger recomposition upon change
* @param lifecycleOwner The lifecycle owner to attach an observer
* @param effects The effects to be launched when we receive the respective event callbacks
*/
@Composable
fun LifecycleResumeEffect(
key1: Any?,
key2: Any?,
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
effects: LifecycleResumePauseEffectScope.() -> LifecyclePauseOrDisposeEffectResult
) {
val lifecycleResumePauseEffectScope = remember(key1, key2, lifecycleOwner) {
LifecycleResumePauseEffectScope(lifecycleOwner.lifecycle)
}
LifecycleResumeEffectImpl(lifecycleOwner, lifecycleResumePauseEffectScope, effects)
}
/**
* Schedule a pair of effects to run when the [Lifecycle] receives either a
* [Lifecycle.Event.ON_RESUME] or [Lifecycle.Event.ON_PAUSE] (or any new unique
* value of [key1] or [key2] or [key3]). The ON_RESUME effect will be the body
* of the [effects] block and the ON_PAUSE effect will be within the
* (onPauseOrDispose clause)[LifecycleResumePauseEffectScope.onPauseOrDispose]:
*
* ```
* LifecycleResumeEffect(lifecycleOwner) {
* // add ON_RESUME effect here
*
* onPauseOrDispose {
* // add clean up for work kicked off in the ON_RESUME effect here
* }
* }
* ```
*
* @sample androidx.lifecycle.compose.samples.lifecycleResumeEffectSample
*
* A [LifecycleResumeEffect] **must** include an
* [onPauseOrDispose][LifecycleResumePauseEffectScope.onPauseOrDispose] clause as
* the final statement in its [effects] block. If your operation does not require
* an effect for _both_ [Lifecycle.Event.ON_RESUME] and [Lifecycle.Event.ON_PAUSE],
* a [LifecycleEventEffect] should be used instead.
*
* A [LifecycleResumeEffect]'s _key_ is a value that defines the identity of the effect.
* If a key changes, the [LifecycleResumeEffect] must
* [dispose][LifecycleResumePauseEffectScope.onPauseOrDispose] its current [effects] and
* reset by calling [effects] again. Examples of keys include:
*
* * Observable objects that the effect subscribes to
* * Unique request parameters to an operation that must cancel and retry if those parameters change
*
* This function uses a [LifecycleEventObserver] to listen for when [LifecycleResumeEffect]
* enters the composition and the effects will be launched when receiving a
* [Lifecycle.Event.ON_RESUME] or [Lifecycle.Event.ON_PAUSE] event, respectively. If the
* [LifecycleResumeEffect] leaves the composition prior to receiving an [Lifecycle.Event.ON_PAUSE]
* event, [onPauseOrDispose][LifecycleResumePauseEffectScope.onPauseOrDispose] will be called
* to clean up the work that was kicked off in the ON_RESUME effect.
*
* This function should **not** be used to launch tasks in response to callback
* events by way of storing callback data as a [Lifecycle.State] in a [MutableState].
* Instead, see [currentStateAsState] to obtain a [State<Lifecycle.State>][State]
* that may be used to launch jobs in response to state changes.
*
* @param key1 A unique value to trigger recomposition upon change
* @param key2 A unique value to trigger recomposition upon change
* @param key3 A unique value to trigger recomposition upon change
* @param lifecycleOwner The lifecycle owner to attach an observer
* @param effects The effects to be launched when we receive the respective event callbacks
*/
@Composable
fun LifecycleResumeEffect(
key1: Any?,
key2: Any?,
key3: Any?,
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
effects: LifecycleResumePauseEffectScope.() -> LifecyclePauseOrDisposeEffectResult
) {
val lifecycleResumePauseEffectScope = remember(key1, key2, key3, lifecycleOwner) {
LifecycleResumePauseEffectScope(lifecycleOwner.lifecycle)
}
LifecycleResumeEffectImpl(lifecycleOwner, lifecycleResumePauseEffectScope, effects)
}
/**
* Schedule a pair of effects to run when the [Lifecycle] receives either a
* [Lifecycle.Event.ON_RESUME] or [Lifecycle.Event.ON_PAUSE] (or any new unique
* value of [keys]). The ON_RESUME effect will be the body of the [effects]
* block and the ON_PAUSE effect will be within the
* (onPauseOrDispose clause)[LifecycleResumePauseEffectScope.onPauseOrDispose]:
*
* ```
* LifecycleResumeEffect(lifecycleOwner) {
* // add ON_RESUME effect here
*
* onPauseOrDispose {
* // add clean up for work kicked off in the ON_RESUME effect here
* }
* }
* ```
*
* @sample androidx.lifecycle.compose.samples.lifecycleResumeEffectSample
*
* A [LifecycleResumeEffect] **must** include an
* [onPauseOrDispose][LifecycleResumePauseEffectScope.onPauseOrDispose] clause as
* the final statement in its [effects] block. If your operation does not require
* an effect for _both_ [Lifecycle.Event.ON_RESUME] and [Lifecycle.Event.ON_PAUSE],
* a [LifecycleEventEffect] should be used instead.
*
* A [LifecycleResumeEffect]'s _key_ is a value that defines the identity of the effect.
* If a key changes, the [LifecycleResumeEffect] must
* [dispose][LifecycleResumePauseEffectScope.onPauseOrDispose] its current [effects] and
* reset by calling [effects] again. Examples of keys include:
*
* * Observable objects that the effect subscribes to
* * Unique request parameters to an operation that must cancel and retry if those parameters change
*
* This function uses a [LifecycleEventObserver] to listen for when [LifecycleResumeEffect]
* enters the composition and the effects will be launched when receiving a
* [Lifecycle.Event.ON_RESUME] or [Lifecycle.Event.ON_PAUSE] event, respectively. If the
* [LifecycleResumeEffect] leaves the composition prior to receiving an [Lifecycle.Event.ON_PAUSE]
* event, [onPauseOrDispose][LifecycleResumePauseEffectScope.onPauseOrDispose] will be called
* to clean up the work that was kicked off in the ON_RESUME effect.
*
* This function should **not** be used to launch tasks in response to callback
* events by way of storing callback data as a [Lifecycle.State] in a [MutableState].
* Instead, see [currentStateAsState] to obtain a [State<Lifecycle.State>][State]
* that may be used to launch jobs in response to state changes.
*
* @param keys The unique values to trigger recomposition upon changes
* @param lifecycleOwner The lifecycle owner to attach an observer
* @param effects The effects to be launched when we receive the respective event callbacks
*/
@Composable
fun LifecycleResumeEffect(
vararg keys: Any?,
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
effects: LifecycleResumePauseEffectScope.() -> LifecyclePauseOrDisposeEffectResult
) {
val lifecycleResumePauseEffectScope = remember(*keys, lifecycleOwner) {
LifecycleResumePauseEffectScope(lifecycleOwner.lifecycle)
}
LifecycleResumeEffectImpl(lifecycleOwner, lifecycleResumePauseEffectScope, effects)
}
@Composable
private fun LifecycleResumeEffectImpl(
lifecycleOwner: LifecycleOwner,
scope: LifecycleResumePauseEffectScope,
effects: LifecycleResumePauseEffectScope.() -> LifecyclePauseOrDisposeEffectResult
) {
DisposableEffect(lifecycleOwner, scope) {
var effectResult: LifecyclePauseOrDisposeEffectResult? = null
val observer = LifecycleEventObserver { _, event ->
when (event) {
Lifecycle.Event.ON_RESUME -> with(scope) {
effectResult = effects()
}
Lifecycle.Event.ON_PAUSE -> effectResult?.runPauseOrOnDisposeEffect()
else -> {}
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
effectResult?.runPauseOrOnDisposeEffect()
}
}
}
/**
* Interface used for [LifecycleResumeEffect] to run the effect within the onPauseOrDispose
* clause when an (ON_PAUSE)[Lifecycle.Event.ON_PAUSE] event is received or when cleanup is
* * needed for the work that was kicked off in the ON_RESUME effect.
*/
interface LifecyclePauseOrDisposeEffectResult {
fun runPauseOrOnDisposeEffect()
}
/**
* Receiver scope for [LifecycleResumeEffect] that offers the [onPauseOrDispose] clause to
* couple the ON_RESUME effect. This should be the last statement in any call to
* [LifecycleResumeEffect].
*
* This scope is also a [LifecycleOwner] to allow access to the
* (lifecycle)[LifecycleResumePauseEffectScope.lifecycle] within the [onPauseOrDispose] clause.
*
* @param lifecycle The lifecycle being observed by this receiver scope
*/
class LifecycleResumePauseEffectScope(override val lifecycle: Lifecycle) : LifecycleOwner {
/**
* Provide the [onPauseOrDisposeEffect] to the [LifecycleResumeEffect] to run when the observer
* receives an (ON_PAUSE)[Lifecycle.Event.ON_PAUSE] event or must undergo cleanup.
*/
inline fun onPauseOrDispose(
crossinline onPauseOrDisposeEffect: LifecycleOwner.() -> Unit
): LifecyclePauseOrDisposeEffectResult = object : LifecyclePauseOrDisposeEffectResult {
override fun runPauseOrOnDisposeEffect() {
onPauseOrDisposeEffect()
}
}
}