InsertKoinIO / koin

Koin - a pragmatic lightweight dependency injection framework for Kotlin & Kotlin Multiplatform
https://insert-koin.io
Apache License 2.0
8.98k stars 712 forks source link

Overriding a declaration which is attached using `includes` in the same module doesn't work as expected #1919

Open AKJAW opened 2 months ago

AKJAW commented 2 months ago

Describe the bug

Having the following code:

interface SomeInterface {

    fun get(): String
}

class DefaultImplementation: SomeInterface {

    override fun get(): String = "Default"
}

class OverriddenImplementation: SomeInterface {

    override fun get(): String = "Overridden"
}

When we want to override the deafult implementations for some flavors of our Application.

In the following way:

val defaultModuleVal = module {
    factoryOf(::DefaultImplementation) bind SomeInterface::class
}

val mainModule = module {
    includes(defaultModuleVal)
    factoryOf(::OverriddenImplementation) bind SomeInterface::class
}

Does not override the declaration, the graph will still use the default implementation.

In order to fix the issue, instead of includes, a Module. extension function could be used

fun Module.defaultModuleFun() {
    factoryOf(::DefaultImplementation) bind SomeInterface::class
}

val mainModule = module {
    defaultModuleFun()
    factoryOf(::OverriddenImplementation) bind SomeInterface::class
}

To Reproduce You can use the code above to reproduce the issue.

Expected behavior using includes should attach the dependencies in the same place where it is declared, so declaring / overriding dependencies after the includes should work as expected.

Or there should be an explicit way of declaring overriding dependencies (like it was before).

Because as it is now, it can cause hidden issues.

Koin module and version: koin-core 3.5.3 and 3.5.6

Snippet or Sample project to help reproduce

interface SomeInterface {

    fun get(): String
}

class DefaultImplementation: SomeInterface {

    override fun get(): String = "Default"
}

class OverriddenImplementation: SomeInterface {

    override fun get(): String = "Overridden"
}

val defaultModuleVal = module {
    factoryOf(::DefaultImplementation) bind SomeInterface::class
}

fun Module.defaultModuleFun() {
    factoryOf(::DefaultImplementation) bind SomeInterface::class
}

val mainModule = module {
    defaultModuleFun()
    factoryOf(::OverriddenImplementation) bind SomeInterface::class
}
arnaudgiuliani commented 2 months ago

includes operator loads the definition you would like to overloadwith?

AKJAW commented 2 months ago

In my opinion that depends on the placement of includes

includes(defaultModuleVal)
factoryOf(::OverriddenImplementation) bind SomeInterface::class

should result in OverriddenImplementation being bound

when includes is last

factoryOf(::OverriddenImplementation) bind SomeInterface::class
includes(defaultModuleVal)

then DefaultImplementation should be bound

That's the assumption from the documentation, whatever is declared later should override whatever came before