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
-
Group by feature/layer
val authModule = module { /* auth feature */ }
val networkModule = module { /* network layer */ } -
Use
includes()for compositionval dataModule = module {
includes(networkModule, databaseModule)
} -
Keep modules focused - Single responsibility per module
Naming
- Use descriptive names:
networkModule,userFeatureModule - Group related:
authDataModule,authDomainModule
Multi-Module Projects
- One public module per feature
- Use
private/internalfor implementation modules - Place shared modules in
:core
Next Steps
- Definitions - Create definitions
- Scopes - Manage lifecycle with scopes
- Best Practices - Module organization patterns