koin icon indicating copy to clipboard operation
koin copied to clipboard

Using bind with generic interfaces does not work correctry

Open shmaltorhbooks opened this issue 1 year ago • 7 comments

Describe the bug When I trying to bind multiple instances to different generic interfaces I<T>, koin returns all instances of I ignoring generic type <T>

Koin module and version: koin-core:3.5.6

Snippet or Sample project to help reproduce

interface SomeHandler<T> {
    fun handle(value: T)
}

class StringsHandler: SomeHandler<String> {
    override fun handle(value: String) = println(value)
}

class LocalDateTimeHandler: SomeHandler<LocalDateTime> {
    override fun handle(value: LocalDateTime) = println(value.format(DateTimeFormatter.ISO_DATE_TIME))
}
import org.koin.core.module.dsl.singleOf
import org.koin.dsl.koinApplication
import org.koin.dsl.module
import org.koin.core.module.dsl.bind
import java.time.LocalDateTime
import kotlin.test.assertTrue
import kotlin.test.*

class ConstructorDLSBindingTest {
    @Test
    fun test_bind_with_generic_interface() {
        val koin = koinApplication {
            modules(module {
                singleOf(::StringsHandler) { bind<SomeHandler<String>>() }
                singleOf(::LocalDateTimeHandler) { bind<SomeHandler<LocalDateTime>>() }
            })
        }.koin

        val textHandlers: List<SomeHandler<String>> = koin.getAll<SomeHandler<String>>()
        val timeHandlers: List<SomeHandler<LocalDateTime>> = koin.getAll<SomeHandler<LocalDateTime>>()

        // fail, actual size is 2
        assertTrue(textHandlers.size == 1)
        // this will throw ClassCastException
        // java.lang.String cannot be cast to java.time.LocalDateTime
        textHandlers.map { it.handle("test") }


        // the same, size == 2, ClassCastException java.time.LocalDateTime cannot be cast to java.lang.String
        assertTrue(timeHandlers.size == 1)
        timeHandlers.map { it.handle(LocalDateTime.now()) }
    }
}

shmaltorhbooks avatar May 25 '24 16:05 shmaltorhbooks

Same issue here.

    val appModule = module {
        singleOf(::MovieRepository)
        singleOf(::UpdateMovieUseCase) { bind<UseCase<List<MovieDomainModel>>>() }
        singleOf(::EditMovieUseCase) { bind<UseCase<EditMovieDomainModel>>() }
    }
    // This does not work
    val updateUseCase = koinInject<UseCase<List<MovieDomainModel>>>()
    // This works
    //val updateUseCase = koinInject<UpdateMovieUseCase>()
    val editUseCase = koinInject<UseCase<EditMovieDomainModel>>()

    println(updateUseCase)
    println(editUseCase)

Will both print 6478-6478 System.out I com.example.composetest.EditMovieUseCase@554e934 6478-6478 System.out I com.example.composetest.EditMovieUseCase@554e934

The issue is when not the implementation is requested but rather the abstraction (interface).

WebTiger89 avatar May 31 '24 21:05 WebTiger89

Will this be supported in future releases?

WebTiger89 avatar Aug 19 '24 15:08 WebTiger89

?

WebTiger89 avatar Oct 17 '24 09:10 WebTiger89

For now this is a limitation, but perhaps we can imagine something that can help a specific injection case.

arnaudgiuliani avatar Nov 18 '24 13:11 arnaudgiuliani

Same problem here.

I have an app with some kind of « namespacing » concept, with use-cases in the form of :

interface SomeUseCase<T: Namespace>

// Only one implementation
class SomeUseCaseImpl<T: Namespace> (someRepository: SomeRepository<T>) : SomeUseCase<T>

I'd like to inject the correct dependencies depending on T. This doesn't work :

object Namespace1 : Namespace
object Namespace2 : Namespace

val diModule = module {
    // Namespace1
    singleOf(::SomeUseCaseImpl) { bind<SomeUseCase<Namespace1>() }
    singleOf(::NameSpace1SomeRepositoryImpl) { bind<SomeRepository<Namespace1>>() }
    
    // Namespace2
    singleOf(::SomeUseCaseImpl) { bind<SomeUseCase<Namespace2>() }
    singleOf(::NameSpace2SomeRepositoryImpl) { bind<SomeRepository<Namespace2>>() }
}

Some limitations with typeOf performances are here. Let's see between new Kotlin typeOf & next gen DSK for Koin

arnaudgiuliani avatar Apr 30 '25 17:04 arnaudgiuliani

And this is why koin is a non-starter. Kodein does not have this limitation.

dalewking avatar May 09 '25 15:05 dalewking

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Oct 07 '25 02:10 stale[bot]