Skip to main content
Version: 1.0.0-beta10

Lifecycle Extensions

Api

One-time suspend functions

Examples

import dispatch.android.*

// This could be any LifecycleOwner -- Fragments, Activities, Services...
class SomeScreen : Fragment() {

init {

// auto-created MainImmediateCoroutineScope which is lifecycle-aware
dispatchLifecycleScope //...

// active only when "resumed". starts a fresh coroutine each time
// this is a rough proxy for LiveData behavior
dispatchLifecycleScope.launchEveryResume { }

// active only when "started". starts a fresh coroutine each time
dispatchLifecycleScope.launchEveryStart { }

// launch when created, automatically stop on destroy
dispatchLifecycleScope.launchEveryCreate { }

// it works as a normal CoroutineScope as well (because it is)
dispatchLifecycleScope.launchMain { }

}
}
class SomeApplication : Application() {
override fun onCreate() {
super.onCreate()
// A custom factory can be set to add elements to the CoroutineContext
LifecycleScopeFactory.set { MainImmediateContext() + SomeCustomElement() }
}
}
class SomeEspressoTest {
@Before
fun setUp() {
// This custom factory can be used to use custom scopes for testing,
// such as an idling dispatcher
LifecycleScopeFactory.set { MainImmediateIdlingCoroutineScope().coroutineContext }
}

@After
fun tearDown() {
// The factory can also be reset to default
LifecycleScopeFactory.reset()
}
}

Difference from AndroidX

This module is really just a slightly different version of androidx-lifecycle-runtime-ktx — the library which gives us the lifecycleScope property.

Why not just use AndroidX? Because we need two things it doesn't offer.

Custom CoroutineScope factories

The way androidx-lifecycle-runtime constructs its CoroutineScope is hard-coded, which eliminates the possibility of using a custom CoroutineContext such as a DispatcherProvider or IdlingDispatcher. With dispatch.android.lifecycle, we can set a custom factory.

class SomeFragmentEspressoTest {

// Not part of this artifact. see dispatch-android-espresso
@JvmField @Rule val idlingRule = IdlingDispatcherProviderRule()

@Before
fun setUp() {
// set a custom factory which is applied to all newly created lifecycleScopes
LifecycleScopeFactory.set {
MainImmediateContext() + idlingRule.dispatcherProvider
}

// now SomeFragment will use an IdlingDispatcher in its CoroutineScope
}
}

Automatic lifecycle jobs

Structured concurrency relies upon cancellation, but androidx-lifecycle-runtime.ktx doesn't cancel. It uses a special PausingDispatcher. This pausing behavior then leaks upstream, creating backpressure and potentially deadlocks.

There's a bug filed in their issue tracker, but 2.2.0 got released anyway.

This library's API surface is the same as that within the AndroidX version, but has a different strategy for handling lifecycle events. When a lifecycle state enters the desired range, such as at ON_RESUME, a new coroutine is created. When the state exists the range, that coroutine is cancelled. If the lifecycle state enters the desired range again, a new coroutine is created.

import dispatch.android.*

class SomeFragment : Fragment() {

val viewModel: SomeViewModel by viewModels()

init {
// automatically created CoroutineScope using the factory described above
dispatchLifecycleScope.launchWhenResumed {
viewModel.someFlow.consume { }
}
}

}

This has the desired effect of not leaking backpressure upstream (which in this example is the viewModel).

Minimum Gradle Config

Add to your module's build.gradle.kts:

repositories {
mavenCentral()
}

dependencies {

implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2")
implementation("com.rickbusarow.dispatch:dispatch-android-lifecycle-extensions:1.0.0-beta10")

implementation("androidx.lifecycle:lifecycle-common:2.2.0")
}