Skip to content

Instantly share code, notes, and snippets.

@marcellogalhardo
Last active June 3, 2021 08:46
Show Gist options
  • Save marcellogalhardo/88cd1b17a41e82acc9913448ee316094 to your computer and use it in GitHub Desktop.
Save marcellogalhardo/88cd1b17a41e82acc9913448ee316094 to your computer and use it in GitHub Desktop.
Utility functions to collect coroutines Flow respecting Android's Lifecycle.
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