dispatch-android-lifecycle-extensions
Contents
#api
#one-time-suspend-functions
#difference-from-androidx
#custom-coroutinescope-factories
#automatic-lifecycle-jobs
#minimum-gradle-config
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.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0")
implementation(platform("com.rickbusarow.dispatch:dispatch-bom:1.0.0-beta10"))
implementation("com.rickbusarow.dispatch:dispatch-android-lifecycle-extensions")
implementation("androidx.lifecycle:lifecycle-common:2.3.1")
}