Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ZakTaccardi/aadfc8675ab8928d11cbca920359f854 to your computer and use it in GitHub Desktop.
Save ZakTaccardi/aadfc8675ab8928d11cbca920359f854 to your computer and use it in GitHub Desktop.
ViewLifecycleScope extensions
import androidx.fragment.app.Fragment
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.CoroutineScope
@Suppress("UNCHECKED_CAST")
internal class ViewLifecycleScopeImpl(
private val fragment: Fragment
) : ViewLifecycleScope {
override fun launchWhenCreated(block: suspend LifecycleOwnerCoroutineScope.() -> Unit) {
fragment.viewLifecycle {
lifecycleScope.launchWhenCreated {
block(LifecycleOwnerCoroutineScopeImpl(this@viewLifecycle, this))
}
}
}
override fun launchWhenStarted(block: suspend LifecycleOwnerCoroutineScope.() -> Unit) {
fragment.viewLifecycle {
lifecycleScope.launchWhenStarted {
block(LifecycleOwnerCoroutineScopeImpl(this@viewLifecycle, this))
}
}
}
override fun launchWhenResumed(block: suspend LifecycleOwnerCoroutineScope.() -> Unit) {
fragment.viewLifecycle {
lifecycleScope.launchWhenResumed {
block(LifecycleOwnerCoroutineScopeImpl(this@viewLifecycle, this))
}
}
}
}
internal class LifecycleOwnerCoroutineScopeImpl(
lifecycleOwner: LifecycleOwner,
coroutineScope: CoroutineScope
) : LifecycleOwnerCoroutineScope, LifecycleOwner by lifecycleOwner, CoroutineScope by coroutineScope
import androidx.fragment.app.Fragment
import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
/**
* Emits the new [LifecycleOwner] to [block] when there is a new View [LifecycleOwner]. Usage in a Fragment should be:
*
* ```kotlin
* init {
* viewLifecycle { // emit new `LifecycleOwner` for the fragments `View`
* // ...
* }
* }
* ```
*
* `onCreateView(..)` will have been completed by the time [block] emits, so it will be safe to access your inflated
* `View`.
*
* Under the hood, [Fragment.getViewLifecycleOwnerLiveData] is used to invoke [block] for each
* emission
*
* @see [Fragment.getViewLifecycleOwner] for more info around the behavior. Whenever this is set,
* [block] will emit.
*/
fun Fragment.viewLifecycle(block: LifecycleOwner.() -> Unit) {
viewLifecycleOwnerLiveData.observe(
this,
Observer { it -> block(it) }
)
}
/**
* Provides a special object that allows for the following API in a fragment:
*
* ```kotlin
* init {
* viewLifecycleScope.launchWhenCreated {
*
* }
* }
* ```
*
* This reduces nesting compared to the [Fragment.viewLifecycle] above.
*/
val Fragment.viewLifecycleScope get(): ViewLifecycleScope = ViewLifecycleScopeImpl(this)
/**
* @see [LifecycleCoroutineScope.launchWhenCreated]
*/
fun LifecycleOwner.launchWhenCreated(block: suspend CoroutineScope.() -> Unit): Job = lifecycleScope
.launchWhenCreated(block)
/**
* @see [LifecycleCoroutineScope.launchWhenStarted]
*/
fun LifecycleOwner.launchWhenStarted(block: suspend CoroutineScope.() -> Unit): Job = lifecycleScope
.launchWhenStarted(block)
/**
* @see [LifecycleCoroutineScope.launchWhenResumed]
*/
fun LifecycleOwner.launchWhenResumed(block: suspend CoroutineScope.() -> Unit): Job = lifecycleScope
.launchWhenResumed(block)
/**
* @see [LifecycleCoroutineScope.launchWhenCreated]
*/
fun <T> Flow<T>.launchWhenCreatedIn(lifecycleOwner: LifecycleOwner): Job = lifecycleOwner.lifecycleScope
.launchWhenCreated {
this@launchWhenCreatedIn.collect()
}
/**
* @see [LifecycleCoroutineScope.launchWhenStarted]
*/
fun <T> Flow<T>.launchWhenStartedIn(lifecycleOwner: LifecycleOwner): Job = lifecycleOwner.lifecycleScope
.launchWhenStarted {
this@launchWhenStartedIn.collect()
}
/**
* @see [LifecycleCoroutineScope.launchWhenResumed]
*/
fun <T> Flow<T>.launchWhenResumedIn(lifecycleOwner: LifecycleOwner): Job = lifecycleOwner.lifecycleScope
.launchWhenResumed {
this@launchWhenResumedIn.collect()
}
/**
* A variant of [LifecycleCoroutineScope] for a [Fragment.getViewLifecycleOwner]
*
* Unfortunately [LifecycleCoroutineScope] is a class, so cannot implement it. Note, this "scope" does not implement
* [CoroutineScope] as a design choice because coroutines may not be immediately available for launching if this is
* referred to when [Fragment.getViewLifecycleOwner] is `null`.
*/
interface ViewLifecycleScope {
/**
* See [LifecycleCoroutineScope.launchWhenCreated]
*/
fun launchWhenCreated(block: suspend LifecycleOwnerCoroutineScope.() -> Unit)
/**
* See [LifecycleCoroutineScope.launchWhenStarted]
*/
fun launchWhenStarted(block: suspend LifecycleOwnerCoroutineScope.() -> Unit)
/**
* See [LifecycleCoroutineScope.launchWhenResumed]
*/
fun launchWhenResumed(block: suspend LifecycleOwnerCoroutineScope.() -> Unit)
}
/**
* A class that is both a [LifecycleOwner] and [CoroutineScope]. Used by [ViewLifecycleScope].
*
* This helps prevent accidental references to [Fragment]'s [LifecycleOwner] when you originally intend to refer to
* [Fragment.getViewLifecycleOwner].
*
* @link https://issuetracker.google.com/issues/153858931 for more info
*/
interface LifecycleOwnerCoroutineScope : LifecycleOwner, CoroutineScope
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment