Introduction
Koin's dependency injection uses different scopes (single, factory, scoped) to control instance lifecycle. When a single is defined in a module that is loaded multiple times, or when scoped is used without proper scope management, multiple instances of what should be a singleton are created. This causes state inconsistency, duplicated resources, and unexpected behavior in repository and service layers.
Symptoms
- Repository has different state when accessed from different ViewModels
- Database instance created multiple times causing connection conflicts
- User session data lost because different instances are used
singledefinition creating new instance after module reload- Memory leak from multiple singleton instances holding resources
Debug Koin instances:
``kotlin
// Check if instances are the same
val repo1 = koin.get<MyRepository>()
val repo2 = koin.get<MyRepository>()
println("Same instance: ${repo1 === repo2}") // Should be true for single
Common Causes
- Module loaded multiple times with
loadKoinModules factoryused instead ofsinglefor stateful dependencies- Koin scope not properly created or closed
viewModeldefinition creating new ViewModel instead of using ViewModelStore- Module redefinition overwriting previous definitions
Step-by-Step Fix
- 1.**Use single for application-wide shared instances":
- 2.```kotlin
- 3.val appModule = module {
- 4.// CORRECT - single instance shared across entire app
- 5.single { MyDatabase(get()) }
- 6.single { MyRepository(get()) }
- 7.single { SharedPreferencesManager(get()) }
// WRONG - factory creates new instance every time // factory { MyRepository(get()) }
// ViewModel - Koin manages lifecycle with ViewModelStore viewModel { MyViewModel(get()) } }
// Load module ONCE in Application class class MyApp : Application() { override fun onCreate() { super.onCreate() startKoin { androidContext(this@MyApp) modules(appModule) // Loaded once at startup } } } ```
- 1.**Fix scoped dependency lifecycle":
- 2.```kotlin
- 3.// WRONG - scoped without proper scope management
- 4.val userModule = module {
- 5.scoped { UserProfileService(get()) } // New instance each time scope is created
- 6.}
// CORRECT - create and manage scope explicitly class UserScope(private val koin: Koin) { private val scope = koin.createScope("user-session", named<UserScope>())
fun getUserProfileService(): UserProfileService { return scope.get() }
fun close() { scope.close() // Cleans up scoped instances } }
// Usage val userScope = UserScope(getKoin()) val service = userScope.getUserProfileService()
// When user logs out userScope.close() ```
- 1.**Debug Koin module loading":
- 2.```kotlin
- 3.// Add Koin logger to detect module loading issues
- 4.startKoin {
- 5.androidContext(this@MyApp)
- 6.// Enable logging
- 7.androidLogger(if (BuildConfig.DEBUG) Level.DEBUG else Level.NONE)
- 8.modules(appModule, networkModule, databaseModule)
- 9.}
// Check what modules are loaded fun printLoadedModules() { val koin = getKoin() koin.instanceRegistry.instances.forEach { (key, value) -> println("Instance: $key -> ${value::class.simpleName}") } }
// Verify singleton behavior @Test fun testSingletonInstance() { val app = MyApplication() app.onCreate()
val repo1 = getKoin().get<MyRepository>() val repo2 = getKoin().get<MyRepository>()
assertSame(repo1, repo2, "Repository should be a singleton") } ```
- 1.**Prevent module reload creating duplicate instances":
- 2.```kotlin
- 3.// WRONG - reloading module creates duplicate definitions
- 4.fun reloadFeature() {
- 5.loadKoinModules(featureModule) // First load
- 6.// ... later
- 7.loadKoinModules(featureModule) // Second load - duplicates!
- 8.}
// CORRECT - check if module is loaded before loading fun loadFeatureIfNotLoaded() { val koin = getKoin() val isLoaded = koin.instanceRegistry.instances .any { it.value is FeatureService }
if (!isLoaded) { loadKoinModules(featureModule) } } ```
Prevention
- Define application-wide dependencies as
singlein modules loaded once - Use
viewModelfor ViewModel-scoped dependencies (Koin manages lifecycle) - Use
scopedonly with explicit scope creation and cleanup - Enable Koin logging in debug builds to detect duplicate loading
- Write unit tests that verify singleton instances are shared
- Document which scope each dependency should use in module definitions