dispatch-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 #extensions.
fun foo(scope: CoroutineScope) {
scope.launchDefault { }
scope.launchIO { }
scope.launchMain { }
scope.launchMainImmediate { }
scope.launchUnconfined { }
}
You can define custom mappings via a #marker-interfaces-and-factories, 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 CoroutineScope 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 {
// ...
}
}
Contents
#types
#marker-interfaces-and-factories
#extensions
#launch
#async
#withcontext
#flow
#defaultdispatcherprovider
#out-of-box-default-functionality
#easy-global-dispatcher-overrides
#minimum-gradle-config
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
Name | Dispatcher |
---|---|
DefaultCoroutineScope | DispatcherProvider.default |
IOCoroutineScope | DispatcherProvider.io |
MainCoroutineScope | DispatcherProvider.main |
MainImmediateCoroutineScope | DispatcherProvider.mainImmediate |
UnconfinedCoroutineScope | DispatcherProvider.unconfined |
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 kotlin.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 kotlin.coroutineContext from the collector in a pattern called context_preservation. These new operators maintain context preservation (they’re forced to, actually), and extract the kotlin.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 CoroutineContextDispatcherProvider 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 CoroutineDispatcher from the Dispatchers singleton.
Easy global dispatcher overrides
DefaultDispatcherProvider
has similar DefaultDispatcherProvider.set/DefaultDispatcherProvider.set 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.