Skip to main content
Version: 4.2

Modules

Koin modules are the building blocks for organizing your dependency injection configuration.

What is a Module?

A module is a logical container for grouping related definitions:

val appModule = module {
single<Database>()
single<UserRepository>()
viewModel<UserViewModel>()
}

Modules help you:

  • Organize definitions by feature or layer
  • Encapsulate related dependencies
  • Reuse configurations across contexts
  • Control visibility in modular projects

Creating Modules

With Compiler Plugin DSL

import org.koin.plugin.module.dsl.*

val networkModule = module {
single<ApiClient>()
single<TokenManager>()
}

val databaseModule = module {
single<Database>()
single<UserDao>()
}

With Annotations

@Module
@ComponentScan("com.myapp.network")
class NetworkModule

@Module
@ComponentScan("com.myapp.database")
class DatabaseModule

With Classic DSL

val networkModule = module {
singleOf(::ApiClient)
singleOf(::TokenManager)
}

Using Multiple Modules

Dependencies can reference definitions from other modules:

// Data layer
val dataModule = module {
single<Database>()
single<UserRepository>() // Can use Database from this module
}

// Presentation layer
val viewModelModule = module {
viewModel<UserViewModel>() // Can use UserRepository from dataModule
}

// Load both
startKoin {
modules(dataModule, viewModelModule)
}
info

Koin resolves dependencies across all loaded modules automatically. No explicit imports needed.

Module Composition with includes()

Compose larger modules from smaller ones:

val networkModule = module {
single<ApiClient>()
}

val storageModule = module {
single<Database>()
}

// Parent module includes child modules
val dataModule = module {
includes(networkModule, storageModule)
single<UserRepository>()
}

// Loading dataModule automatically loads networkModule and storageModule
startKoin {
modules(dataModule)
}

Multi-Module Projects

Use visibility modifiers to control what's exposed:

// :feature:user module

// Private - hidden from other modules
private val userDataModule = module {
single<UserDao>()
single<UserCache>()
}

// Public API
val userFeatureModule = module {
includes(userDataModule)
viewModel<UserViewModel>()
}
// :app module
startKoin {
modules(userFeatureModule) // Only this is accessible
}

Module Override

Default Behavior

By default, the last loaded definition wins:

val productionModule = module {
single<ApiService> { ProductionApi() }
}

val debugModule = module {
single<ApiService> { DebugApi() }
}

startKoin {
modules(productionModule, debugModule) // DebugApi wins
}

Strict Mode

Disable overrides in production:

startKoin {
allowOverride(false) // Throws exception on override attempt
modules(productionModule)
}

Explicit Override

Allow specific overrides in strict mode:

val testModule = module {
single<ApiService> { MockApi() }.override() // Allowed
}

startKoin {
allowOverride(false)
modules(productionModule, testModule)
}

Eager Module Creation

Create singletons immediately at startup:

val coreModule = module(createdAtStart = true) {
single<ConfigManager>()
single<LoggingSystem>()
}

Parameterized Modules

Create modules dynamically:

fun featureModule(debug: Boolean) = module {
single<Logger> {
if (debug) DebugLogger() else ProductionLogger()
}
}

startKoin {
modules(featureModule(debug = BuildConfig.DEBUG))
}

Strategy Pattern

Use modules to swap implementations:

val repositoryModule = module {
single<UserRepository>() // Depends on Datasource
}

// Strategy options
val localDatasourceModule = module {
single<Datasource> { LocalDatasource() }
}

val remoteDatasourceModule = module {
single<Datasource> { RemoteDatasource() }
}

// Production
startKoin {
modules(repositoryModule, remoteDatasourceModule)
}

// Offline mode
startKoin {
modules(repositoryModule, localDatasourceModule)
}

Annotated Modules

Basic Module

@Module
class AppModule

With Component Scanning

@Module
@ComponentScan("com.myapp") // Scan package for annotations
class AppModule

Auto-Discovery with Configuration

@Module
@ComponentScan
@Configuration // Auto-discovered, no need to list
class FeatureModule

Using Annotated Modules

import org.koin.ksp.generated.*

startKoin {
modules(AppModule().module)
}

// Or with @Configuration - auto-discovered
@KoinApplication
object MyApp

fun main() {
MyApp.startKoin()
}

Best Practices

Organization

  1. Group by feature/layer

    val authModule = module { /* auth feature */ }
    val networkModule = module { /* network layer */ }
  2. Use includes() for composition

    val dataModule = module {
    includes(networkModule, databaseModule)
    }
  3. Keep modules focused - Single responsibility per module

Naming

  • Use descriptive names: networkModule, userFeatureModule
  • Group related: authDataModule, authDomainModule

Multi-Module Projects

  1. One public module per feature
  2. Use private/internal for implementation modules
  3. Place shared modules in :core

Next Steps