Last active
June 3, 2021 08:46
-
-
Save marcellogalhardo/88cd1b17a41e82acc9913448ee316094 to your computer and use it in GitHub Desktop.
Utility functions to collect coroutines Flow respecting Android's Lifecycle.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package dev.marcellogalhardo.lifecycle | |
import android.app.Activity | |
import androidx.lifecycle.Lifecycle | |
import androidx.lifecycle.LifecycleCoroutineScope | |
import androidx.lifecycle.LifecycleOwner | |
import kotlinx.coroutines.CoroutineScope | |
import kotlinx.coroutines.Job | |
import kotlinx.coroutines.channels.Channel | |
import kotlinx.coroutines.flow.Flow | |
import kotlinx.coroutines.flow.collect | |
import kotlinx.coroutines.flow.launchIn | |
import kotlinx.coroutines.launch | |
/** | |
* Adds a given [observer] in a pair with a [LifecycleOwner], and this [observer] will be notified | |
* about modifications of the wrapped data only if the paired [LifecycleOwner] is in active state. | |
* | |
* A [LifecycleOwner] is considered as active, if its state is [Lifecycle.State.STARTED] or | |
* [Lifecycle.State.RESUMED]. | |
* | |
* If the [lifecycleScope] moves to the [Lifecycle.State.DESTROYED] state, the observer will | |
* automatically be removed. | |
* | |
* When data changes while the [lifecycleScope] is not active, it will not receive any updates. | |
* If it becomes active again, it will receive the last available data automatically. | |
* | |
* [observeIn] keeps a strong reference to the observer and the owner as long as the | |
* given [LifecycleOwner] is not destroyed. When it is destroyed, [observeIn] removes references | |
* to the observer and the owner. | |
* | |
* If the given [lifecycleScope] is already in [Lifecycle.State.DESTROYED] state, [observeIn] | |
* ignores the call. If the given [lifecycleScope], [observer] tuple is already added, the call is | |
* ignored. If the [observer] is already in the list with another [lifecycleScope], [observeIn] | |
* throws an [IllegalArgumentException]. | |
* | |
* TODO: Should use new ways to collect when they hit stable. | |
* See here: https://medium.com/androiddevelopers/a-safer-way-to-collect-flows-from-android-uis-23080b1f8bda | |
* | |
* @param lifecycleScope: the [LifecycleCoroutineScope] which controls the observer. | |
* @param observer: the observer that will receive the events. | |
*/ | |
inline fun <T> Flow<T>.observeIn( | |
lifecycleScope: LifecycleCoroutineScope, | |
crossinline observer: suspend (value: T) -> Unit | |
): Job { | |
return lifecycleScope.launchWhenStarted { collect(observer) } | |
} | |
/** | |
* @see launchIn | |
*/ | |
inline fun <T> Flow<T>.collectIn( | |
scope: CoroutineScope, | |
crossinline action: suspend (value: T) -> Unit, | |
): Job { | |
return scope.launch { collect(action) } | |
} | |
/** | |
* Launches and runs the given [Flow] when the [Lifecycle] controlling this | |
* [LifecycleCoroutineScope] is at least in [Lifecycle.State.CREATED] state. | |
* | |
* For an [Activity], this state will be reached in two cases: | |
* - after [Activity.onCreate] call; | |
* - right before [Activity.onStop] call. | |
* | |
* The returned [Job] will be cancelled when the [Lifecycle] is [Lifecycle.State.DESTROYED]. | |
* @see launchIn | |
* @see androidx.lifecycle.LifecycleCoroutineScope.launchWhenCreated | |
* @see androidx.lifecycle.whenCreated | |
* @see androidx.lifecycle.Lifecycle.State.CREATED | |
*/ | |
fun <T> Flow<T>.launchWhenCreatedIn( | |
lifecycleScope: LifecycleCoroutineScope, | |
): Job { | |
return lifecycleScope.launchWhenCreated { collect() } | |
} | |
/** | |
* @see launchWhenCreatedIn | |
*/ | |
inline fun <T> Flow<T>.collectWhenCreatedIn( | |
lifecycleScope: LifecycleCoroutineScope, | |
crossinline action: suspend (value: T) -> Unit, | |
): Job { | |
return lifecycleScope.launchWhenCreated { collect(action) } | |
} | |
/** | |
* Launches and runs the given [Flow] when the [Lifecycle] controlling this | |
* [LifecycleCoroutineScope] is at least in [Lifecycle.State.STARTED] state. | |
* | |
* For an [Activity], this state will be reached in two cases: | |
* - after [Activity.onStart] call; | |
* - right before [Activity.onPause] call. | |
* | |
* The returned [Job] will be cancelled when the [Lifecycle] is [Lifecycle.State.DESTROYED]. | |
* @see launchIn | |
* @see androidx.lifecycle.LifecycleCoroutineScope.launchWhenStarted | |
* @see androidx.lifecycle.whenStarted | |
* @see androidx.lifecycle.Lifecycle.State.STARTED | |
*/ | |
fun <T> Flow<T>.launchWhenStartedIn( | |
lifecycleScope: LifecycleCoroutineScope, | |
): Job { | |
return lifecycleScope.launchWhenStarted { collect() } | |
} | |
/** | |
* @see launchWhenStartedIn | |
*/ | |
inline fun <T> Flow<T>.collectWhenStartedIn( | |
lifecycleScope: LifecycleCoroutineScope, | |
crossinline action: suspend (value: T) -> Unit, | |
): Job { | |
return lifecycleScope.launchWhenStarted { collect(action) } | |
} | |
/** | |
* Launches and runs the given [Flow] when the [Lifecycle] controlling this | |
* [LifecycleCoroutineScope] is at least in [Lifecycle.State.RESUMED] state. | |
* | |
* For an [Activity], this state will be reached in one case: | |
* - after [Activity.onResume] is called. | |
* | |
* The returned [Job] will be cancelled when the [Lifecycle] is [Lifecycle.State.DESTROYED]. | |
* @see launchIn | |
* @see androidx.lifecycle.LifecycleCoroutineScope.launchWhenResumed | |
* @see androidx.lifecycle.whenResumed | |
* @see androidx.lifecycle.Lifecycle.State.RESUMED | |
*/ | |
fun <T> Flow<T>.launchWhenResumedIn( | |
lifecycleScope: LifecycleCoroutineScope, | |
): Job { | |
return lifecycleScope.launchWhenResumed { collect() } | |
} | |
/** | |
* @see launchWhenResumedIn | |
*/ | |
inline fun <T> Flow<T>.collectWhenResumedIn( | |
lifecycleScope: LifecycleCoroutineScope, | |
crossinline action: suspend (value: T) -> Unit, | |
): Job { | |
return lifecycleScope.launchWhenResumed { collect(action) } | |
} | |
/** | |
* @see Channel.send | |
* @see launchIn | |
*/ | |
internal fun <T> Channel<T>.sendIn( | |
coroutineScope: CoroutineScope, | |
element: T, | |
): Job { | |
return coroutineScope.launch { send(element) } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment