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