Koin Watcher
When any new injectable class is created, it MUST be registered in the correct Koin module. Forgetting this causes runtime crashes — Koin fails silently at compile time.
Module Ownership — Register Here
| What you're adding | Module file | Variable name |
|---|---|---|
| New Service, DataSource, Repository | core/data/src/commonMain/.../di/CoreDataModule.kt | coreDataModule |
| New auth-related class | core/auth/src/commonMain/.../di/CoreAuthModule.kt | coreAuthModule |
| New shared presentation utility | core/presentation/src/commonMain/.../di/CorePresentationModule.kt | corePresentationModule |
| New feature ViewModel or UseCase | feature/{name}/src/commonMain/.../di/{Name}FeatureModule.kt | {name}FeatureModule |
| Platform-specific (Android/iOS only) | actual val in the appropriate androidMain or iosMain DI file | — |
Scopes — Use the Right One
- •
single { }/singleOf(::Class)— One instance shared app-wide. Use for: Clients, Services, DataSources, Repositories, Database. - •
factory { }/factoryOf(::Class)— New instance every time it's requested. Use for: ViewModels, UseCases.
Prefer factoryOf(::Class) and singleOf(::Class) — Koin auto-injects constructor parameters. Only use the single { } / factory { } lambda form when manual wiring is needed (e.g., passing a specific parameter or named qualifier).
New Feature Module Checklist
When a new feature module is created, two things must happen:
- •Create
feature/{name}/src/commonMain/.../di/{Name}FeatureModule.kt - •Add
{name}FeatureModuleto the list inshared/src/commonMain/.../di/KoinHelper.kt
If step 2 is missed, the entire feature module is invisible to Koin at runtime.
Platform-Specific Modules
When a dependency has Android/iOS variants, use the expect/actual pattern:
- •Declare
expect val myModule: ModuleincommonMain - •Implement
actual val myModulein bothandroidMainandiosMain - •Include the
expectval in the parent module viaincludes(myModule)
Existing examples: databaseModule in CoreDataModule, secureStorageModule in CoreAuthModule.
Interface Registration
Always register by interface, not by implementation:
// Correct
single<MyRepository> { MyRepositoryImpl(get(), get()) }
singleOf(::MyRepositoryImpl).bind<MyRepository>()
// Wrong — leaks implementation details into the DI graph
single { MyRepositoryImpl(get(), get()) }