Core
Never reference Dispatchers again, and never inject a dispatchers
interface into your classes.
All the standard CoroutineDispatcher types are embedded in a CoroutineContext and can be accessed explicitly or via convenient extension functions.
fun foo(scope: CoroutineScope) {
scope.launchDefault { }
scope.launchIO { }
scope.launchMain { }
scope.launchMainImmediate { }
scope.launchUnconfined { }
}
You can define custom mappings via a factory, making testing much easier, or use the default, which simply maps to Dispatchers.
// a standard DispatcherProvider is easy to create
val myDefaultDispatchProvider = DispatcherProvider()
// but they're also very extensible. This version is interchangeable and is convenient in some test scenarios.
val myCustomDispatcherProvider = object: DispatcherProvider {
override val default: CoroutineDispatcher = newSingleThreadCoroutineContext("default")
override val io: CoroutineDispatcher = newSingleThreadCoroutineContext("io")
override val main: CoroutineDispatcher get() = newSingleThreadCoroutineContext("main")
override val mainImmediate: CoroutineDispatcher get() = newSingleThreadCoroutineContext("mainImmediate")
override val unconfined: CoroutineDispatcher = newSingleThreadCoroutineContext("unconfined")
}
Custom CoroutineScopes interfaces allow for more granularity when defining a class or function with a CoroutineScope dependency.
There are also factory functions for conveniently creating any implementation, with a built-in DispatcherProvider.
val mainScope = MainCoroutineScope()
val someUIClass = SomeUIClass(mainScope)
class SomeUIClass(val coroutineScope: MainCoroutineScope) {
fun foo() = coroutineScope.launch {
// ...
}
}
Types
Name | Description |
---|---|
DispatcherProvider | Interface which provides the 5 standard CoroutineDispatcher properties of the Dispatchers object, but which can be embedded in a CoroutineContext |
DefaultDispatcherProvider | Mutable singleton holder for an implementation of DispatcherProvider. By default, it simply delegates to the corresponding properties in the Dispatchers singleton. Whenever a CoroutineContext does not have a DispatcherProvider, this singleton's value will be used by default. |
Marker interfaces and factories
Extensions
| | Default | IO | Main | Main.immediate |
Unconfined |
| ------------ | --------------- | ---------- | ------------ | --------------------- | ------------------ |
| Job | launchDefault | launchIO | launchMain | launchMainImmediate | launchUnconfined
| Deferred | asyncDefault | asyncIO | asyncMain | asyncMainImmediate | asyncUnconfined
| suspend T
| withDefault | withIO | withMain | withMainImmediate | withUnconfined
| Flow<T>
| flowOnDefault | flowOnIO | flowOnMain | flowOnMainImmediate | flowOnUnconfined
Launch
fun foo(scope: CoroutineScope) {
scope.launchDefault { }
scope.launchIO { }
scope.launchMain { }
scope.launchMainImmediate { }
scope.launchUnconfined { }
}
Async
fun foo(scope: CoroutineScope) {
scope.asyncDefault { }
scope.asyncIO { }
scope.asyncMain { }
scope.asyncMainImmediate { }
scope.asyncUnconfined { }
}
WithContext
The CoroutineContext used for withContext comes from
the coroutineContext top-level suspend property in kotlin.coroutines.
It returns the current context, so the default
, io
, etc. used here are the ones defined in
the CoroutineScope of the caller. There is no need to inject any other dependencies.
suspend fun foo() {
// note that we have no CoroutineContext
withDefault { }
withIO { }
withMain { }
withMainImmediate { }
withUnconfined { }
}
Flow
Like withContext, Flow typically doesn’t get a CoroutineScope of its own. They inherit the coroutineContext from the collector in a pattern called context preservation. These new operators maintain context preservation (they’re forced to, actually), and extract the coroutineContext from the collector.
val someFlow = flow { }
.flowOnDefault()
.flowOnIO()
.flowOnMain()
.flowOnMainImmediate()
.flowOnUnconfined()
DefaultDispatcherProvider
The simplest way to get up and running with Dispatch. All library access to a CoroutineContext's DispatcherProvider filters through a single extension property:
public val CoroutineContext.dispatcherProvider: DispatcherProvider
get() = get(DispatcherProvider) ?: DefaultDispatcherProvider.get()
If the receiver does not have a DispatcherProvider
, the value from DefaultDispatcherProvider
will be returned. In practice, this brings at least two benefits:
Out-of-box default functionality
Calls such as launchIO { ... }
or withMain { ... }
are safe to use (guaranteed to have a
DispatcherProvider
) regardless of the source of the CoroutineContext
or CoroutineScope and
without any additional configuration. By default, they will access the corresponding
CoroutineDispatchers from the Dispatchers singleton.
Easy global dispatcher overrides
DefaultDispatcherProvider
has similar
set/reset functionality to the
Dispatchers.setMain/Dispatchers.resetMain extensions in kotlinx-coroutines-test
, except it
doesn't need to be confined to testing.
You can use DefaultDispatcherProvider.set to globally set a custom implementation at the beginning of an application's lifecycle, but the most likely use-case is certainly in testing.
@Test
fun `my test`() = runBlocking {
DefaultDispatcherProvider.set(TestDispatcherProvider())
withMain {
// this would normally crash without using Dispatchers.setMain
// but "main" here comes from the TestDispatcherProvider created above -- not Dispatchers.Main
}
DefaultDispatcherProvider.reset() // from dispatch-test
}
See dispatch-test and TestDispatcherProvider
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-core:1.0.0-beta10-SNAPSHOT")
}