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

NameDescription
DispatcherProviderInterface which provides the 5 standard CoroutineDispatcher properties of the Dispatchers object, but which can be embedded in a CoroutineContext
DefaultDispatcherProviderMutable 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

NameDispatcher
DefaultCoroutineScopeDispatcherProvider.default
IOCoroutineScopeDispatcherProvider.io
MainCoroutineScopeDispatcherProvider.main
MainImmediateCoroutineScopeDispatcherProvider.mainImmediate
UnconfinedCoroutineScopeDispatcherProvider.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.

Packages

Link copied to clipboard