11package com.hoc081098.channeleventbus.sample.android.common
22
33import androidx.compose.runtime.Composable
4+ import androidx.compose.runtime.Immutable
45import androidx.compose.runtime.LaunchedEffect
56import androidx.compose.runtime.NonRestartableComposable
67import androidx.compose.runtime.RememberObserver
7- import androidx.compose.runtime.getValue
88import androidx.compose.runtime.remember
99import androidx.compose.runtime.rememberUpdatedState
1010import androidx.compose.ui.platform.LocalLifecycleOwner
1111import androidx.lifecycle.Lifecycle
1212import androidx.lifecycle.LifecycleOwner
1313import androidx.lifecycle.repeatOnLifecycle
14+ import kotlinx.coroutines.CoroutineDispatcher
1415import kotlinx.coroutines.CoroutineScope
1516import kotlinx.coroutines.Dispatchers
1617import kotlinx.coroutines.Job
1718import kotlinx.coroutines.cancel
1819import kotlinx.coroutines.flow.Flow
1920import kotlinx.coroutines.launch
2021
22+ @Immutable
23+ enum class CollectWithLifecycleEffectDispatcher {
24+ /* *
25+ * Use [Dispatchers.Main][kotlinx.coroutines.MainCoroutineDispatcher].
26+ */
27+ Main ,
28+
29+ /* *
30+ * Use [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate].
31+ */
32+ ImmediateMain ,
33+
34+ /* *
35+ * Use [androidx.compose.runtime.Composer.applyCoroutineContext].
36+ * Under the hood, it uses Compose [androidx.compose.ui.platform.AndroidUiDispatcher].
37+ */
38+ Composer ,
39+ }
40+
2141/* *
2242 * Collect the given [Flow] in an effect that runs when [LifecycleOwner.lifecycle] is at least at [minActiveState].
2343 *
24- * If [inImmediateMain] is `true`, the effect will run in
25- * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate],
26- * otherwise it will run in [androidx.compose.runtime.Composer.applyCoroutineContext].
44+ * - If [dispatcher] is [CollectWithLifecycleEffectDispatcher.ImmediateMain], the effect will run in
45+ * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate].
46+ * - If [dispatcher] is [CollectWithLifecycleEffectDispatcher.Main], the effect will run in
47+ * [Dispatchers.Main][kotlinx.coroutines.MainCoroutineDispatcher].
48+ * - If [dispatcher] is [CollectWithLifecycleEffectDispatcher.Composer], the effect will run in
49+ * [androidx.compose.runtime.Composer.applyCoroutineContext].
50+ *
51+ * NOTE: When [dispatcher] or [collector] changes, the effect will **NOT** be restarted.
52+ * The latest [collector] will be used to receive values from the [Flow] ([rememberUpdatedState] is used).
53+ * If you want to restart the effect, you need to change [keys].
2754 *
2855 * @param keys Keys to be used to [remember] the effect.
2956 * @param lifecycleOwner The [LifecycleOwner] to be used to [repeatOnLifecycle].
3057 * @param minActiveState The minimum [Lifecycle.State] to be used to [repeatOnLifecycle].
31- * @param inImmediateMain Whether the effect should run in
32- * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate].
58+ * @param dispatcher The dispatcher to be used to launch the [Flow].
3359 * @param collector The collector to be used to collect the [Flow].
3460 *
3561 * @see [LaunchedEffect]
62+ * @see [CollectWithLifecycleEffectDispatcher]
3663 */
3764@Composable
3865fun <T > Flow<T>.CollectWithLifecycleEffect (
3966 vararg keys : Any? ,
4067 lifecycleOwner : LifecycleOwner = LocalLifecycleOwner .current,
4168 minActiveState : Lifecycle .State = Lifecycle .State .STARTED ,
42- inImmediateMain : Boolean = true ,
69+ dispatcher : CollectWithLifecycleEffectDispatcher = CollectWithLifecycleEffectDispatcher . ImmediateMain ,
4370 collector : (T ) -> Unit ,
4471) {
4572 val flow = this
46- val currentCollector by rememberUpdatedState(collector)
73+ val collectorState = rememberUpdatedState(collector)
4774
4875 val block: suspend CoroutineScope .() -> Unit = {
4976 lifecycleOwner.repeatOnLifecycle(minActiveState) {
50- flow.collect(currentCollector)
77+ // NOTE: we don't use `flow.collect(collectState.value)` because it can use the old value
78+ flow.collect { collectorState.value(it) }
5179 }
5280 }
5381
54- if (inImmediateMain) {
55- LaunchedEffectInImmediateMain (flow, lifecycleOwner, minActiveState, * keys, block = block)
56- } else {
57- LaunchedEffect (flow, lifecycleOwner, minActiveState, * keys, block = block)
82+ when (dispatcher) {
83+ CollectWithLifecycleEffectDispatcher .ImmediateMain -> {
84+ LaunchedEffectInImmediateMain (flow, lifecycleOwner, minActiveState, * keys, block = block)
85+ }
86+
87+ CollectWithLifecycleEffectDispatcher .Main -> {
88+ LaunchedEffectInMain (flow, lifecycleOwner, minActiveState, * keys, block = block)
89+ }
90+
91+ CollectWithLifecycleEffectDispatcher .Composer -> {
92+ LaunchedEffect (flow, lifecycleOwner, minActiveState, * keys, block = block)
93+ }
5894 }
5995}
6096
@@ -65,13 +101,24 @@ private fun LaunchedEffectInImmediateMain(
65101 vararg keys : Any? ,
66102 block : suspend CoroutineScope .() -> Unit ,
67103) {
68- remember(* keys) { LaunchedEffectImpl (block) }
104+ remember(* keys) { LaunchedEffectImpl (block, Dispatchers .Main .immediate) }
105+ }
106+
107+ @Composable
108+ @NonRestartableComposable
109+ @Suppress(" ArrayReturn" )
110+ private fun LaunchedEffectInMain (
111+ vararg keys : Any? ,
112+ block : suspend CoroutineScope .() -> Unit ,
113+ ) {
114+ remember(* keys) { LaunchedEffectImpl (block, Dispatchers .Main ) }
69115}
70116
71117private class LaunchedEffectImpl (
72118 private val task : suspend CoroutineScope .() -> Unit ,
119+ dispatcher : CoroutineDispatcher ,
73120) : RememberObserver {
74- private val scope = CoroutineScope (Dispatchers . Main .immediate )
121+ private val scope = CoroutineScope (dispatcher )
75122 private var job: Job ? = null
76123
77124 override fun onRemembered () {
0 commit comments