DispatcherModule

@Module
object DispatcherModule

Hilt module providing qualified coroutine dispatchers for dependency injection.

Why This Module Exists

While Kotlin provides Dispatchers.IO, Dispatchers.Default, and Dispatchers.Main directly, injecting them via Hilt offers several advantages:

  1. Testability: Tests can inject TestDispatcher to control coroutine execution

  2. Flexibility: Can swap dispatcher implementations without changing consuming code

  3. Consistency: Enforces using the correct dispatcher for each task type

  4. Type Safety: Qualifiers prevent accidental dispatcher misuse

Usage Guidelines

IoDispatcher - For I/O Operations

Use for blocking I/O tasks:

  • Network requests (Retrofit calls, HTTP downloads)

  • Database operations (Room queries, inserts, updates)

  • File system operations (reading/writing files)

  • DataStore reads/writes

class Repository @Inject constructor(
@IoDispatcher private val ioDispatcher: CoroutineDispatcher
) {
suspend fun fetchData() = withContext(ioDispatcher) {
database.query() // Runs on IO dispatcher
}
}

DefaultDispatcher - For CPU-Intensive Tasks

Use for computational work:

  • JSON parsing (large payloads)

  • Data transformation (mapping large lists)

  • Image processing

  • Complex calculations

class DataProcessor @Inject constructor(
@DefaultDispatcher private val defaultDispatcher: CoroutineDispatcher
) {
suspend fun processLargeDataset() = withContext(defaultDispatcher) {
data.map { /* complex transformation */}
}
}

MainDispatcher - For UI Updates

Rarely needed in this architecture! Most UI updates happen via:

  • StateFlow.collectAsStateWithLifecycle() (automatically dispatches to Main)

  • Compose recomposition (automatically on Main thread)

Only use when you need explicit main thread dispatch:

class LegacyViewHelper @Inject constructor(
@MainDispatcher private val mainDispatcher: CoroutineDispatcher
) {
suspend fun updateView() = withContext(mainDispatcher) {
// Direct view manipulation
}
}

Architecture Decision

This template uses injected dispatchers instead of hardcoded Dispatchers.IO because:

  • Repositories can be unit tested with StandardTestDispatcher

  • Tests run synchronously and deterministically

  • No need for runTest or advanceUntilIdle() when dispatchers are injected

  • Future flexibility to add custom dispatchers (e.g., limited concurrency)

See also

Functions

Link copied to clipboard
@Provides
fun providesDefaultDispatcher(): CoroutineDispatcher

Provides the default coroutine dispatcher for CPU-intensive operations.

Link copied to clipboard
@Provides
fun providesIoDispatcher(): CoroutineDispatcher

Provides the I/O coroutine dispatcher for blocking I/O operations.

Link copied to clipboard
@Provides
fun providesMainDispatcher(): CoroutineDispatcher

Provides the main coroutine dispatcher for UI thread operations.