-
-
Notifications
You must be signed in to change notification settings - Fork 68
Description
Describe the bug
When using Koin Annotations with @Single(binds = [...]) on a provider function that returns a generic type, Koin creates separate singleton instances for each interface instead of sharing the same underlying object. This happens even when the concrete class implements all the bound interfaces.
To Reproduce
Steps to reproduce the behavior:
- Define generic interfaces and a concrete implementation:
interface CacheReader<K, V> {
fun get(key: K): V?
}
interface CacheWriter<K, V> {
fun put(key: K, value: V)
}
class Cache<K, V> : CacheReader<K, V>, CacheWriter<K, V> {
private val storage = mutableMapOf<K, V>()
override fun get(key: K) = storage[key]
override fun put(key: K, value: V) { storage[key] = value }
}
data class Account(val id: String, val name: String)- Create a provider with @single(binds = [...]):
@Single(binds = [CacheReader::class, CacheWriter::class])
fun provideAccountCache() = Cache<String, Account>()- Inject both interfaces:
class MyViewModel(
private val reader: CacheReader<String, Account>,
private val writer: CacheWriter<String, Account>
) {
init {
println(reader === writer) }
}
}- Observe that reader and writer are different instances.
Expected behavior
@single(binds = [...]) should bind the same singleton instance to all specified interfaces, so injecting any bound interface returns the same object.
Koin project used and used version (please complete the following information):
- Koin Annotations (KSP) version: 2.3.1
- Koin version: 4.1.0
- Kotlin version: 2.2.21
Additional moduleDefinition
Workaround that currently works:
@Single
fun provideAccountCache() = Cache<String, Account>()
class MyViewModel(accountCache: Cache<String, Account>) {
val accountCacheReader: CacheReader<String, Account> = accountCache
val accountCacheWriter: CacheWriter<String, Account> = accountCache
}This guarantees that CacheReader and CacheWriter share the same instance but requires casting in the consumer.