1 /*
<lambda>null2 * Copyright 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package androidx.lifecycle.compose
18
19 import androidx.compose.runtime.Composable
20 import androidx.compose.runtime.DisposableEffect
21 import androidx.compose.runtime.MutableState
22 import androidx.compose.runtime.State
23 import androidx.compose.runtime.getValue
24 import androidx.compose.runtime.remember
25 import androidx.compose.runtime.rememberUpdatedState
26 import androidx.lifecycle.Lifecycle
27 import androidx.lifecycle.LifecycleEventObserver
28 import androidx.lifecycle.LifecycleOwner
29
30 /**
31 * Schedule an effect to run when the [Lifecycle] receives a specific [Lifecycle.Event].
32 *
33 * Using a [LifecycleEventObserver] to listen for when [LifecycleEventEffect] enters the
34 * composition, [onEvent] will be launched when receiving the specified [event].
35 *
36 * This function should **not** be used to listen for [Lifecycle.Event.ON_DESTROY] because Compose
37 * stops recomposing after receiving a [Lifecycle.Event.ON_STOP] and will never be aware of an
38 * ON_DESTROY to launch [onEvent].
39 *
40 * This function should also **not** be used to launch tasks in response to callback events by way
41 * of storing callback data as a [Lifecycle.State] in a [MutableState]. Instead, see
42 * [currentStateAsState] to obtain a [State<Lifecycle.State>][State] that may be used to launch jobs
43 * in response to state changes.
44 *
45 * @sample androidx.lifecycle.compose.samples.lifecycleEventEffectSample
46 * @param event The [Lifecycle.Event] to listen for
47 * @param lifecycleOwner The lifecycle owner to attach an observer
48 * @param onEvent The effect to be launched when we receive an [event] callback
49 * @throws IllegalArgumentException if attempting to listen for [Lifecycle.Event.ON_DESTROY]
50 */
51 @Composable
52 public fun LifecycleEventEffect(
53 event: Lifecycle.Event,
54 lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
55 onEvent: () -> Unit
56 ) {
57 if (event == Lifecycle.Event.ON_DESTROY) {
58 throw IllegalArgumentException(
59 "LifecycleEventEffect cannot be used to " +
60 "listen for Lifecycle.Event.ON_DESTROY, since Compose disposes of the " +
61 "composition before ON_DESTROY observers are invoked."
62 )
63 }
64
65 // Safely update the current `onEvent` lambda when a new one is provided
66 val currentOnEvent by rememberUpdatedState(onEvent)
67 DisposableEffect(lifecycleOwner) {
68 val observer = LifecycleEventObserver { _, e ->
69 if (e == event) {
70 currentOnEvent()
71 }
72 }
73
74 lifecycleOwner.lifecycle.addObserver(observer)
75
76 onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
77 }
78 }
79
80 /**
81 * Schedule a pair of effects to run when the [Lifecycle] receives either a
82 * [Lifecycle.Event.ON_START] or [Lifecycle.Event.ON_STOP] (or any new unique value of [key1]). The
83 * ON_START effect will be the body of the [effects] block and the ON_STOP effect will be within the
84 * (onStopOrDispose clause)[LifecycleStartStopEffectScope.onStopOrDispose]:
85 * ```
86 * LifecycleStartEffect(lifecycleOwner) {
87 * // add ON_START effect here
88 *
89 * onStopOrDispose {
90 * // add clean up for work kicked off in the ON_START effect here
91 * }
92 * }
93 * ```
94 *
95 * @sample androidx.lifecycle.compose.samples.lifecycleStartEffectSample
96 *
97 * A [LifecycleStartEffect] **must** include an
98 * [onStopOrDispose][LifecycleStartStopEffectScope.onStopOrDispose] clause as the final statement in
99 * its [effects] block. If your operation does not require an effect for _both_
100 * [Lifecycle.Event.ON_START] and [Lifecycle.Event.ON_STOP], a [LifecycleEventEffect] should be used
101 * instead.
102 *
103 * A [LifecycleStartEffect]'s _key_ is a value that defines the identity of the effect. If the key
104 * changes, the [LifecycleStartEffect] must [dispose][LifecycleStartStopEffectScope.onStopOrDispose]
105 * its current [effects] and reset by calling [effects] again. Examples of keys include:
106 * * Observable objects that the effect subscribes to
107 * * Unique request parameters to an operation that must cancel and retry if those parameters change
108 *
109 * This function uses a [LifecycleEventObserver] to listen for when [LifecycleStartEffect] enters
110 * the composition and the effects will be launched when receiving a [Lifecycle.Event.ON_START] or
111 * [Lifecycle.Event.ON_STOP] event, respectively. If the [LifecycleStartEffect] leaves the
112 * composition prior to receiving an [Lifecycle.Event.ON_STOP] event,
113 * [onStopOrDispose][LifecycleStartStopEffectScope.onStopOrDispose] will be called to clean up the
114 * work that was kicked off in the ON_START effect.
115 *
116 * This function should **not** be used to launch tasks in response to callback events by way of
117 * storing callback data as a [Lifecycle.State] in a [MutableState]. Instead, see
118 * [currentStateAsState] to obtain a [State<Lifecycle.State>][State] that may be used to launch jobs
119 * in response to state changes.
120 *
121 * @param key1 The unique value to trigger recomposition upon change
122 * @param lifecycleOwner The lifecycle owner to attach an observer
123 * @param effects The effects to be launched when we receive the respective event callbacks
124 */
125 @Composable
LifecycleStartEffectnull126 public fun LifecycleStartEffect(
127 key1: Any?,
128 lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
129 effects: LifecycleStartStopEffectScope.() -> LifecycleStopOrDisposeEffectResult
130 ) {
131 val lifecycleStartStopEffectScope =
132 remember(key1, lifecycleOwner) { LifecycleStartStopEffectScope(lifecycleOwner.lifecycle) }
133 LifecycleStartEffectImpl(lifecycleOwner, lifecycleStartStopEffectScope, effects)
134 }
135
136 /**
137 * Schedule a pair of effects to run when the [Lifecycle] receives either a
138 * [Lifecycle.Event.ON_START] or [Lifecycle.Event.ON_STOP] (or any new unique value of [key1] or
139 * [key2]). The ON_START effect will be the body of the [effects] block and the ON_STOP effect will
140 * be within the (onStopOrDispose clause)[LifecycleStartStopEffectScope.onStopOrDispose]:
141 * ```
142 * LifecycleStartEffect(lifecycleOwner) {
143 * // add ON_START effect here
144 *
145 * onStopOrDispose {
146 * // add clean up for work kicked off in the ON_START effect here
147 * }
148 * }
149 * ```
150 *
151 * @sample androidx.lifecycle.compose.samples.lifecycleStartEffectSample
152 *
153 * A [LifecycleStartEffect] **must** include an
154 * [onStopOrDispose][LifecycleStartStopEffectScope.onStopOrDispose] clause as the final statement in
155 * its [effects] block. If your operation does not require an effect for _both_
156 * [Lifecycle.Event.ON_START] and [Lifecycle.Event.ON_STOP], a [LifecycleEventEffect] should be used
157 * instead.
158 *
159 * A [LifecycleStartEffect]'s _key_ is a value that defines the identity of the effect. If a key
160 * changes, the [LifecycleStartEffect] must [dispose][LifecycleStartStopEffectScope.onStopOrDispose]
161 * its current [effects] and reset by calling [effects] again. Examples of keys include:
162 * * Observable objects that the effect subscribes to
163 * * Unique request parameters to an operation that must cancel and retry if those parameters change
164 *
165 * This function uses a [LifecycleEventObserver] to listen for when [LifecycleStartEffect] enters
166 * the composition and the effects will be launched when receiving a [Lifecycle.Event.ON_START] or
167 * [Lifecycle.Event.ON_STOP] event, respectively. If the [LifecycleStartEffect] leaves the
168 * composition prior to receiving an [Lifecycle.Event.ON_STOP] event,
169 * [onStopOrDispose][LifecycleStartStopEffectScope.onStopOrDispose] will be called to clean up the
170 * work that was kicked off in the ON_START effect.
171 *
172 * This function should **not** be used to launch tasks in response to callback events by way of
173 * storing callback data as a [Lifecycle.State] in a [MutableState]. Instead, see
174 * [currentStateAsState] to obtain a [State<Lifecycle.State>][State] that may be used to launch jobs
175 * in response to state changes.
176 *
177 * @param key1 A unique value to trigger recomposition upon change
178 * @param key2 A unique value to trigger recomposition upon change
179 * @param lifecycleOwner The lifecycle owner to attach an observer
180 * @param effects The effects to be launched when we receive the respective event callbacks
181 */
182 @Composable
LifecycleStartEffectnull183 public fun LifecycleStartEffect(
184 key1: Any?,
185 key2: Any?,
186 lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
187 effects: LifecycleStartStopEffectScope.() -> LifecycleStopOrDisposeEffectResult
188 ) {
189 val lifecycleStartStopEffectScope =
190 remember(key1, key2, lifecycleOwner) {
191 LifecycleStartStopEffectScope(lifecycleOwner.lifecycle)
192 }
193 LifecycleStartEffectImpl(lifecycleOwner, lifecycleStartStopEffectScope, effects)
194 }
195
196 /**
197 * Schedule a pair of effects to run when the [Lifecycle] receives either a
198 * [Lifecycle.Event.ON_START] or [Lifecycle.Event.ON_STOP] (or any new unique value of [key1] or
199 * [key2] or [key3]). The ON_START effect will be the body of the [effects] block and the ON_STOP
200 * effect will be within the (onStopOrDispose
201 * clause)[LifecycleStartStopEffectScope.onStopOrDispose]:
202 * ```
203 * LifecycleStartEffect(lifecycleOwner) {
204 * // add ON_START effect here
205 *
206 * onStopOrDispose {
207 * // add clean up for work kicked off in the ON_START effect here
208 * }
209 * }
210 * ```
211 *
212 * @sample androidx.lifecycle.compose.samples.lifecycleStartEffectSample
213 *
214 * A [LifecycleStartEffect] **must** include an
215 * [onStopOrDispose][LifecycleStartStopEffectScope.onStopOrDispose] clause as the final statement in
216 * its [effects] block. If your operation does not require an effect for _both_
217 * [Lifecycle.Event.ON_START] and [Lifecycle.Event.ON_STOP], a [LifecycleEventEffect] should be used
218 * instead.
219 *
220 * A [LifecycleStartEffect]'s _key_ is a value that defines the identity of the effect. If a key
221 * changes, the [LifecycleStartEffect] must [dispose][LifecycleStartStopEffectScope.onStopOrDispose]
222 * its current [effects] and reset by calling [effects] again. Examples of keys include:
223 * * Observable objects that the effect subscribes to
224 * * Unique request parameters to an operation that must cancel and retry if those parameters change
225 *
226 * This function uses a [LifecycleEventObserver] to listen for when [LifecycleStartEffect] enters
227 * the composition and the effects will be launched when receiving a [Lifecycle.Event.ON_START] or
228 * [Lifecycle.Event.ON_STOP] event, respectively. If the [LifecycleStartEffect] leaves the
229 * composition prior to receiving an [Lifecycle.Event.ON_STOP] event,
230 * [onStopOrDispose][LifecycleStartStopEffectScope.onStopOrDispose] will be called to clean up the
231 * work that was kicked off in the ON_START effect.
232 *
233 * This function should **not** be used to launch tasks in response to callback events by way of
234 * storing callback data as a [Lifecycle.State] in a [MutableState]. Instead, see
235 * [currentStateAsState] to obtain a [State<Lifecycle.State>][State] that may be used to launch jobs
236 * in response to state changes.
237 *
238 * @param key1 The unique value to trigger recomposition upon change
239 * @param key2 The unique value to trigger recomposition upon change
240 * @param key3 The unique value to trigger recomposition upon change
241 * @param lifecycleOwner The lifecycle owner to attach an observer
242 * @param effects The effects to be launched when we receive the respective event callbacks
243 */
244 @Composable
LifecycleStartEffectnull245 public fun LifecycleStartEffect(
246 key1: Any?,
247 key2: Any?,
248 key3: Any?,
249 lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
250 effects: LifecycleStartStopEffectScope.() -> LifecycleStopOrDisposeEffectResult
251 ) {
252 val lifecycleStartStopEffectScope =
253 remember(key1, key2, key3, lifecycleOwner) {
254 LifecycleStartStopEffectScope(lifecycleOwner.lifecycle)
255 }
256 LifecycleStartEffectImpl(lifecycleOwner, lifecycleStartStopEffectScope, effects)
257 }
258
259 /**
260 * Schedule a pair of effects to run when the [Lifecycle] receives either a
261 * [Lifecycle.Event.ON_START] or [Lifecycle.Event.ON_STOP] (or any new unique value of [keys]). The
262 * ON_START effect will be the body of the [effects] block and the ON_STOP effect will be within the
263 * (onStopOrDispose clause)[LifecycleStartStopEffectScope.onStopOrDispose]:
264 * ```
265 * LifecycleStartEffect(lifecycleOwner) {
266 * // add ON_START effect here
267 *
268 * onStopOrDispose {
269 * // add clean up for work kicked off in the ON_START effect here
270 * }
271 * }
272 * ```
273 *
274 * @sample androidx.lifecycle.compose.samples.lifecycleStartEffectSample
275 *
276 * A [LifecycleStartEffect] **must** include an
277 * [onStopOrDispose][LifecycleStartStopEffectScope.onStopOrDispose] clause as the final statement in
278 * its [effects] block. If your operation does not require an effect for _both_
279 * [Lifecycle.Event.ON_START] and [Lifecycle.Event.ON_STOP], a [LifecycleEventEffect] should be used
280 * instead.
281 *
282 * A [LifecycleStartEffect]'s _key_ is a value that defines the identity of the effect. If a key
283 * changes, the [LifecycleStartEffect] must [dispose][LifecycleStartStopEffectScope.onStopOrDispose]
284 * its current [effects] and reset by calling [effects] again. Examples of keys include:
285 * * Observable objects that the effect subscribes to
286 * * Unique request parameters to an operation that must cancel and retry if those parameters change
287 *
288 * This function uses a [LifecycleEventObserver] to listen for when [LifecycleStartEffect] enters
289 * the composition and the effects will be launched when receiving a [Lifecycle.Event.ON_START] or
290 * [Lifecycle.Event.ON_STOP] event, respectively. If the [LifecycleStartEffect] leaves the
291 * composition prior to receiving an [Lifecycle.Event.ON_STOP] event,
292 * [onStopOrDispose][LifecycleStartStopEffectScope.onStopOrDispose] will be called to clean up the
293 * work that was kicked off in the ON_START effect.
294 *
295 * This function should **not** be used to launch tasks in response to callback events by way of
296 * storing callback data as a [Lifecycle.State] in a [MutableState]. Instead, see
297 * [currentStateAsState] to obtain a [State<Lifecycle.State>][State] that may be used to launch jobs
298 * in response to state changes.
299 *
300 * @param keys The unique values to trigger recomposition upon changes
301 * @param lifecycleOwner The lifecycle owner to attach an observer
302 * @param effects The effects to be launched when we receive the respective event callbacks
303 */
304 @Composable
LifecycleStartEffectnull305 public fun LifecycleStartEffect(
306 vararg keys: Any?,
307 lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
308 effects: LifecycleStartStopEffectScope.() -> LifecycleStopOrDisposeEffectResult
309 ) {
310 val lifecycleStartStopEffectScope =
311 remember(*keys, lifecycleOwner) { LifecycleStartStopEffectScope(lifecycleOwner.lifecycle) }
312 LifecycleStartEffectImpl(lifecycleOwner, lifecycleStartStopEffectScope, effects)
313 }
314
315 private const val LifecycleStartEffectNoParamError =
316 "LifecycleStartEffect must provide one or more 'key' parameters that define the identity of " +
317 "the LifecycleStartEffect and determine when its previous effect coroutine should be " +
318 "cancelled and a new effect launched for the new key."
319
320 /**
321 * It is an error to call [LifecycleStartEffect] without at least one `key` parameter.
322 *
323 * This deprecated-error function shadows the varargs overload so that the varargs version is not
324 * used without key parameters.
325 *
326 * @see LifecycleStartEffect
327 */
328 @Deprecated(LifecycleStartEffectNoParamError, level = DeprecationLevel.ERROR)
329 @Composable
330 @Suppress("UNUSED_PARAMETER")
LifecycleStartEffectnull331 public fun LifecycleStartEffect(
332 lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
333 effects: LifecycleStartStopEffectScope.() -> LifecycleStopOrDisposeEffectResult
334 ): Unit = error(LifecycleStartEffectNoParamError)
335
336 @Composable
337 private fun LifecycleStartEffectImpl(
338 lifecycleOwner: LifecycleOwner,
339 scope: LifecycleStartStopEffectScope,
340 effects: LifecycleStartStopEffectScope.() -> LifecycleStopOrDisposeEffectResult
341 ) {
342 DisposableEffect(lifecycleOwner, scope) {
343 var effectResult: LifecycleStopOrDisposeEffectResult? = null
344 val observer = LifecycleEventObserver { _, event ->
345 when (event) {
346 Lifecycle.Event.ON_START -> with(scope) { effectResult = effects() }
347 Lifecycle.Event.ON_STOP -> {
348 effectResult?.runStopOrDisposeEffect()
349 effectResult = null
350 }
351 else -> {}
352 }
353 }
354
355 lifecycleOwner.lifecycle.addObserver(observer)
356
357 onDispose {
358 lifecycleOwner.lifecycle.removeObserver(observer)
359 effectResult?.runStopOrDisposeEffect()
360 }
361 }
362 }
363
364 /**
365 * Interface used for [LifecycleStartEffect] to run the effect within the onStopOrDispose clause
366 * when an (ON_STOP)[Lifecycle.Event.ON_STOP] event is received or when cleanup is needed for the
367 * work that was kicked off in the ON_START effect.
368 */
369 public interface LifecycleStopOrDisposeEffectResult {
runStopOrDisposeEffectnull370 public fun runStopOrDisposeEffect()
371 }
372
373 /**
374 * Receiver scope for [LifecycleStartEffect] that offers the [onStopOrDispose] clause to couple the
375 * ON_START effect. This should be the last statement in any call to [LifecycleStartEffect].
376 *
377 * This scope is also a [LifecycleOwner] to allow access to the
378 * (lifecycle)[LifecycleStartStopEffectScope.lifecycle] within the [onStopOrDispose] clause.
379 *
380 * @param lifecycle The lifecycle being observed by this receiver scope
381 */
382 public class LifecycleStartStopEffectScope(override val lifecycle: Lifecycle) : LifecycleOwner {
383 /**
384 * Provide the [onStopOrDisposeEffect] to the [LifecycleStartEffect] to run when the observer
385 * receives an (ON_STOP)[Lifecycle.Event.ON_STOP] event or must undergo cleanup.
386 */
387 public inline fun onStopOrDispose(
388 crossinline onStopOrDisposeEffect: LifecycleOwner.() -> Unit
389 ): LifecycleStopOrDisposeEffectResult =
390 object : LifecycleStopOrDisposeEffectResult {
391 override fun runStopOrDisposeEffect() {
392 onStopOrDisposeEffect()
393 }
394 }
395 }
396
397 /**
398 * Schedule a pair of effects to run when the [Lifecycle] receives either a
399 * [Lifecycle.Event.ON_RESUME] or [Lifecycle.Event.ON_PAUSE] (or any new unique value of [key1]).
400 * The ON_RESUME effect will be the body of the [effects] block and the ON_PAUSE effect will be
401 * within the (onPauseOrDispose clause)[LifecycleResumePauseEffectScope.onPauseOrDispose]:
402 * ```
403 * LifecycleResumeEffect(lifecycleOwner) {
404 * // add ON_RESUME effect here
405 *
406 * onPauseOrDispose {
407 * // add clean up for work kicked off in the ON_RESUME effect here
408 * }
409 * }
410 * ```
411 *
412 * @sample androidx.lifecycle.compose.samples.lifecycleResumeEffectSample
413 *
414 * A [LifecycleResumeEffect] **must** include an
415 * [onPauseOrDispose][LifecycleResumePauseEffectScope.onPauseOrDispose] clause as the final
416 * statement in its [effects] block. If your operation does not require an effect for _both_
417 * [Lifecycle.Event.ON_RESUME] and [Lifecycle.Event.ON_PAUSE], a [LifecycleEventEffect] should be
418 * used instead.
419 *
420 * A [LifecycleResumeEffect]'s _key_ is a value that defines the identity of the effect. If a key
421 * changes, the [LifecycleResumeEffect] must
422 * [dispose][LifecycleResumePauseEffectScope.onPauseOrDispose] its current [effects] and reset by
423 * calling [effects] again. Examples of keys include:
424 * * Observable objects that the effect subscribes to
425 * * Unique request parameters to an operation that must cancel and retry if those parameters change
426 *
427 * This function uses a [LifecycleEventObserver] to listen for when [LifecycleResumeEffect] enters
428 * the composition and the effects will be launched when receiving a [Lifecycle.Event.ON_RESUME] or
429 * [Lifecycle.Event.ON_PAUSE] event, respectively. If the [LifecycleResumeEffect] leaves the
430 * composition prior to receiving an [Lifecycle.Event.ON_PAUSE] event,
431 * [onPauseOrDispose][LifecycleResumePauseEffectScope.onPauseOrDispose] will be called to clean up
432 * the work that was kicked off in the ON_RESUME effect.
433 *
434 * This function should **not** be used to launch tasks in response to callback events by way of
435 * storing callback data as a [Lifecycle.State] in a [MutableState]. Instead, see
436 * [currentStateAsState] to obtain a [State<Lifecycle.State>][State] that may be used to launch jobs
437 * in response to state changes.
438 *
439 * @param key1 The unique value to trigger recomposition upon change
440 * @param lifecycleOwner The lifecycle owner to attach an observer
441 * @param effects The effects to be launched when we receive the respective event callbacks
442 */
443 @Composable
LifecycleResumeEffectnull444 public fun LifecycleResumeEffect(
445 key1: Any?,
446 lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
447 effects: LifecycleResumePauseEffectScope.() -> LifecyclePauseOrDisposeEffectResult
448 ) {
449 val lifecycleResumePauseEffectScope =
450 remember(key1, lifecycleOwner) { LifecycleResumePauseEffectScope(lifecycleOwner.lifecycle) }
451 LifecycleResumeEffectImpl(lifecycleOwner, lifecycleResumePauseEffectScope, effects)
452 }
453
454 /**
455 * Schedule a pair of effects to run when the [Lifecycle] receives either a
456 * [Lifecycle.Event.ON_RESUME] or [Lifecycle.Event.ON_PAUSE] (or any new unique value of [key1] or
457 * [key2]). The ON_RESUME effect will be the body of the [effects] block and the ON_PAUSE effect
458 * will be within the (onPauseOrDispose clause)[LifecycleResumePauseEffectScope.onPauseOrDispose]:
459 * ```
460 * LifecycleResumeEffect(lifecycleOwner) {
461 * // add ON_RESUME effect here
462 *
463 * onPauseOrDispose {
464 * // add clean up for work kicked off in the ON_RESUME effect here
465 * }
466 * }
467 * ```
468 *
469 * @sample androidx.lifecycle.compose.samples.lifecycleResumeEffectSample
470 *
471 * A [LifecycleResumeEffect] **must** include an
472 * [onPauseOrDispose][LifecycleResumePauseEffectScope.onPauseOrDispose] clause as the final
473 * statement in its [effects] block. If your operation does not require an effect for _both_
474 * [Lifecycle.Event.ON_RESUME] and [Lifecycle.Event.ON_PAUSE], a [LifecycleEventEffect] should be
475 * used instead.
476 *
477 * A [LifecycleResumeEffect]'s _key_ is a value that defines the identity of the effect. If a key
478 * changes, the [LifecycleResumeEffect] must
479 * [dispose][LifecycleResumePauseEffectScope.onPauseOrDispose] its current [effects] and reset by
480 * calling [effects] again. Examples of keys include:
481 * * Observable objects that the effect subscribes to
482 * * Unique request parameters to an operation that must cancel and retry if those parameters change
483 *
484 * This function uses a [LifecycleEventObserver] to listen for when [LifecycleResumeEffect] enters
485 * the composition and the effects will be launched when receiving a [Lifecycle.Event.ON_RESUME] or
486 * [Lifecycle.Event.ON_PAUSE] event, respectively. If the [LifecycleResumeEffect] leaves the
487 * composition prior to receiving an [Lifecycle.Event.ON_PAUSE] event,
488 * [onPauseOrDispose][LifecycleResumePauseEffectScope.onPauseOrDispose] will be called to clean up
489 * the work that was kicked off in the ON_RESUME effect.
490 *
491 * This function should **not** be used to launch tasks in response to callback events by way of
492 * storing callback data as a [Lifecycle.State] in a [MutableState]. Instead, see
493 * [currentStateAsState] to obtain a [State<Lifecycle.State>][State] that may be used to launch jobs
494 * in response to state changes.
495 *
496 * @param key1 A unique value to trigger recomposition upon change
497 * @param key2 A unique value to trigger recomposition upon change
498 * @param lifecycleOwner The lifecycle owner to attach an observer
499 * @param effects The effects to be launched when we receive the respective event callbacks
500 */
501 @Composable
LifecycleResumeEffectnull502 public fun LifecycleResumeEffect(
503 key1: Any?,
504 key2: Any?,
505 lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
506 effects: LifecycleResumePauseEffectScope.() -> LifecyclePauseOrDisposeEffectResult
507 ) {
508 val lifecycleResumePauseEffectScope =
509 remember(key1, key2, lifecycleOwner) {
510 LifecycleResumePauseEffectScope(lifecycleOwner.lifecycle)
511 }
512 LifecycleResumeEffectImpl(lifecycleOwner, lifecycleResumePauseEffectScope, effects)
513 }
514
515 /**
516 * Schedule a pair of effects to run when the [Lifecycle] receives either a
517 * [Lifecycle.Event.ON_RESUME] or [Lifecycle.Event.ON_PAUSE] (or any new unique value of [key1] or
518 * [key2] or [key3]). The ON_RESUME effect will be the body of the [effects] block and the ON_PAUSE
519 * effect will be within the (onPauseOrDispose
520 * clause)[LifecycleResumePauseEffectScope.onPauseOrDispose]:
521 * ```
522 * LifecycleResumeEffect(lifecycleOwner) {
523 * // add ON_RESUME effect here
524 *
525 * onPauseOrDispose {
526 * // add clean up for work kicked off in the ON_RESUME effect here
527 * }
528 * }
529 * ```
530 *
531 * @sample androidx.lifecycle.compose.samples.lifecycleResumeEffectSample
532 *
533 * A [LifecycleResumeEffect] **must** include an
534 * [onPauseOrDispose][LifecycleResumePauseEffectScope.onPauseOrDispose] clause as the final
535 * statement in its [effects] block. If your operation does not require an effect for _both_
536 * [Lifecycle.Event.ON_RESUME] and [Lifecycle.Event.ON_PAUSE], a [LifecycleEventEffect] should be
537 * used instead.
538 *
539 * A [LifecycleResumeEffect]'s _key_ is a value that defines the identity of the effect. If a key
540 * changes, the [LifecycleResumeEffect] must
541 * [dispose][LifecycleResumePauseEffectScope.onPauseOrDispose] its current [effects] and reset by
542 * calling [effects] again. Examples of keys include:
543 * * Observable objects that the effect subscribes to
544 * * Unique request parameters to an operation that must cancel and retry if those parameters change
545 *
546 * This function uses a [LifecycleEventObserver] to listen for when [LifecycleResumeEffect] enters
547 * the composition and the effects will be launched when receiving a [Lifecycle.Event.ON_RESUME] or
548 * [Lifecycle.Event.ON_PAUSE] event, respectively. If the [LifecycleResumeEffect] leaves the
549 * composition prior to receiving an [Lifecycle.Event.ON_PAUSE] event,
550 * [onPauseOrDispose][LifecycleResumePauseEffectScope.onPauseOrDispose] will be called to clean up
551 * the work that was kicked off in the ON_RESUME effect.
552 *
553 * This function should **not** be used to launch tasks in response to callback events by way of
554 * storing callback data as a [Lifecycle.State] in a [MutableState]. Instead, see
555 * [currentStateAsState] to obtain a [State<Lifecycle.State>][State] that may be used to launch jobs
556 * in response to state changes.
557 *
558 * @param key1 A unique value to trigger recomposition upon change
559 * @param key2 A unique value to trigger recomposition upon change
560 * @param key3 A unique value to trigger recomposition upon change
561 * @param lifecycleOwner The lifecycle owner to attach an observer
562 * @param effects The effects to be launched when we receive the respective event callbacks
563 */
564 @Composable
LifecycleResumeEffectnull565 public fun LifecycleResumeEffect(
566 key1: Any?,
567 key2: Any?,
568 key3: Any?,
569 lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
570 effects: LifecycleResumePauseEffectScope.() -> LifecyclePauseOrDisposeEffectResult
571 ) {
572 val lifecycleResumePauseEffectScope =
573 remember(key1, key2, key3, lifecycleOwner) {
574 LifecycleResumePauseEffectScope(lifecycleOwner.lifecycle)
575 }
576 LifecycleResumeEffectImpl(lifecycleOwner, lifecycleResumePauseEffectScope, effects)
577 }
578
579 /**
580 * Schedule a pair of effects to run when the [Lifecycle] receives either a
581 * [Lifecycle.Event.ON_RESUME] or [Lifecycle.Event.ON_PAUSE] (or any new unique value of [keys]).
582 * The ON_RESUME effect will be the body of the [effects] block and the ON_PAUSE effect will be
583 * within the (onPauseOrDispose clause)[LifecycleResumePauseEffectScope.onPauseOrDispose]:
584 * ```
585 * LifecycleResumeEffect(lifecycleOwner) {
586 * // add ON_RESUME effect here
587 *
588 * onPauseOrDispose {
589 * // add clean up for work kicked off in the ON_RESUME effect here
590 * }
591 * }
592 * ```
593 *
594 * @sample androidx.lifecycle.compose.samples.lifecycleResumeEffectSample
595 *
596 * A [LifecycleResumeEffect] **must** include an
597 * [onPauseOrDispose][LifecycleResumePauseEffectScope.onPauseOrDispose] clause as the final
598 * statement in its [effects] block. If your operation does not require an effect for _both_
599 * [Lifecycle.Event.ON_RESUME] and [Lifecycle.Event.ON_PAUSE], a [LifecycleEventEffect] should be
600 * used instead.
601 *
602 * A [LifecycleResumeEffect]'s _key_ is a value that defines the identity of the effect. If a key
603 * changes, the [LifecycleResumeEffect] must
604 * [dispose][LifecycleResumePauseEffectScope.onPauseOrDispose] its current [effects] and reset by
605 * calling [effects] again. Examples of keys include:
606 * * Observable objects that the effect subscribes to
607 * * Unique request parameters to an operation that must cancel and retry if those parameters change
608 *
609 * This function uses a [LifecycleEventObserver] to listen for when [LifecycleResumeEffect] enters
610 * the composition and the effects will be launched when receiving a [Lifecycle.Event.ON_RESUME] or
611 * [Lifecycle.Event.ON_PAUSE] event, respectively. If the [LifecycleResumeEffect] leaves the
612 * composition prior to receiving an [Lifecycle.Event.ON_PAUSE] event,
613 * [onPauseOrDispose][LifecycleResumePauseEffectScope.onPauseOrDispose] will be called to clean up
614 * the work that was kicked off in the ON_RESUME effect.
615 *
616 * This function should **not** be used to launch tasks in response to callback events by way of
617 * storing callback data as a [Lifecycle.State] in a [MutableState]. Instead, see
618 * [currentStateAsState] to obtain a [State<Lifecycle.State>][State] that may be used to launch jobs
619 * in response to state changes.
620 *
621 * @param keys The unique values to trigger recomposition upon changes
622 * @param lifecycleOwner The lifecycle owner to attach an observer
623 * @param effects The effects to be launched when we receive the respective event callbacks
624 */
625 @Composable
LifecycleResumeEffectnull626 public fun LifecycleResumeEffect(
627 vararg keys: Any?,
628 lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
629 effects: LifecycleResumePauseEffectScope.() -> LifecyclePauseOrDisposeEffectResult
630 ) {
631 val lifecycleResumePauseEffectScope =
632 remember(*keys, lifecycleOwner) {
633 LifecycleResumePauseEffectScope(lifecycleOwner.lifecycle)
634 }
635 LifecycleResumeEffectImpl(lifecycleOwner, lifecycleResumePauseEffectScope, effects)
636 }
637
638 private const val LifecycleResumeEffectNoParamError =
639 "LifecycleResumeEffect must provide one or more 'key' parameters that define the identity of " +
640 "the LifecycleResumeEffect and determine when its previous effect coroutine should be " +
641 "cancelled and a new effect launched for the new key."
642
643 /**
644 * It is an error to call [LifecycleStartEffect] without at least one `key` parameter.
645 *
646 * This deprecated-error function shadows the varargs overload so that the varargs version is not
647 * used without key parameters.
648 *
649 * @see LifecycleResumeEffect
650 */
651 @Deprecated(LifecycleResumeEffectNoParamError, level = DeprecationLevel.ERROR)
652 @Composable
653 @Suppress("UNUSED_PARAMETER")
LifecycleResumeEffectnull654 public fun LifecycleResumeEffect(
655 lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
656 effects: LifecycleResumePauseEffectScope.() -> LifecyclePauseOrDisposeEffectResult
657 ): Unit = error(LifecycleResumeEffectNoParamError)
658
659 @Composable
660 private fun LifecycleResumeEffectImpl(
661 lifecycleOwner: LifecycleOwner,
662 scope: LifecycleResumePauseEffectScope,
663 effects: LifecycleResumePauseEffectScope.() -> LifecyclePauseOrDisposeEffectResult
664 ) {
665 DisposableEffect(lifecycleOwner, scope) {
666 var effectResult: LifecyclePauseOrDisposeEffectResult? = null
667 val observer = LifecycleEventObserver { _, event ->
668 when (event) {
669 Lifecycle.Event.ON_RESUME -> with(scope) { effectResult = effects() }
670 Lifecycle.Event.ON_PAUSE -> {
671 effectResult?.runPauseOrOnDisposeEffect()
672 effectResult = null
673 }
674 else -> {}
675 }
676 }
677
678 lifecycleOwner.lifecycle.addObserver(observer)
679
680 onDispose {
681 lifecycleOwner.lifecycle.removeObserver(observer)
682 effectResult?.runPauseOrOnDisposeEffect()
683 }
684 }
685 }
686
687 /**
688 * Interface used for [LifecycleResumeEffect] to run the effect within the onPauseOrDispose clause
689 * when an (ON_PAUSE)[Lifecycle.Event.ON_PAUSE] event is received or when cleanup is
690 * * needed for the work that was kicked off in the ON_RESUME effect.
691 */
692 public interface LifecyclePauseOrDisposeEffectResult {
runPauseOrOnDisposeEffectnull693 public fun runPauseOrOnDisposeEffect()
694 }
695
696 /**
697 * Receiver scope for [LifecycleResumeEffect] that offers the [onPauseOrDispose] clause to couple
698 * the ON_RESUME effect. This should be the last statement in any call to [LifecycleResumeEffect].
699 *
700 * This scope is also a [LifecycleOwner] to allow access to the
701 * (lifecycle)[LifecycleResumePauseEffectScope.lifecycle] within the [onPauseOrDispose] clause.
702 *
703 * @param lifecycle The lifecycle being observed by this receiver scope
704 */
705 public class LifecycleResumePauseEffectScope(override val lifecycle: Lifecycle) : LifecycleOwner {
706 /**
707 * Provide the [onPauseOrDisposeEffect] to the [LifecycleResumeEffect] to run when the observer
708 * receives an (ON_PAUSE)[Lifecycle.Event.ON_PAUSE] event or must undergo cleanup.
709 */
710 public inline fun onPauseOrDispose(
711 crossinline onPauseOrDisposeEffect: LifecycleOwner.() -> Unit
712 ): LifecyclePauseOrDisposeEffectResult =
713 object : LifecyclePauseOrDisposeEffectResult {
714 override fun runPauseOrOnDisposeEffect() {
715 onPauseOrDisposeEffect()
716 }
717 }
718 }
719