Koin: Add modules dynamically to the graph

Created on 5 Apr 2019  路  13Comments  路  Source: InsertKoinIO/koin

Hi,
I haven't been able to find this information through the docs, although I believe could be related to Koin context isolation.
Issue #224 might have clues as well, but again, couldn't figure it out.
I'm trying to find out how once we define the main app module and invoke startKoin:
startKoin { modules(appModule) }
then another Feature module could add it's own dependencies to the graph (this module will require some instances from the main app module as well).
We have an app based on Dagger and we would like to migrate it to Koin. In dagger, we basically declare the common components within a base module and the feature components, in different module, have a depends relationship with the base. The idea it's that the main app/base module won't have to import the different module from the features.

I've tried to do the following within the custom Application:

val myApp = koinApplication {           
            androidContext(this@MyApplication)
            modules(appModule)
            MyKoinContext.koinApp = this
        }

then each feature module would get an instance of MyKoinContext.koinApp and add its particular module, but I've had no luck.
I could see this working, maybe with different gradle flavours but I'm trying to avoid that option
since won't scale properly.
Any guide will be appreciated!

core

Most helpful comment

I'm running into the same issue. Would be great if we can either:

1: Have a method to unload modules loaded via loadKoinModules (probably call this at Activity#onDestory when isFinishing or ViewModel#onCleared

2: Choose to not throw an exception when a module definition is found, and we're calling loadKoinModules

@arnaudgiuliani The internal check to not override existing modules is perfect, though a boolean to be able to ignore the exception would be great.

In a multi-module dynamic Android App, I could do this in my Activity:
loadKoinModules(ignoreDefinitionExists = true)

Right now, the problem is that a component outside Koin doesn't know if it's module definitions have been loaded into Koin, and no way to check if it fails other than wrapping in a try-catch.

I'd love to pick this feature up if up for contribution! Doesn't seem like a major change.

All 13 comments

Ok, I might have found the solution:
I just declare the following within MyCustomApplication:

       startKoin {
            modules(appModule)
        }

And then the activity within Feature module 1 just does:
GlobalContext.get().modules(feature1Module) where:

val appModule = module {
    single<CommonRepository> { CommonRepositoryImpl() }
}
val feature1Module = module {
    factory<Feature1Repository> { Feature1RepositoryImpl(get()) }
}
 Feature1RepositoryImpl has a dependency on CommonRepository

Isn't the loadKoinModules() function supposed to be used for this? It is used to load Koin modules dynamically.

@luislukas @arnaudgiuliani If I have a dynamic feature module with only Fragments, no Activities, then where would I call the loadKoinModules() function?

EDIT: Maybe this issue can help: https://github.com/InsertKoinIO/koin/issues/420

@haroldadmin Seems that loadKoinModules() it's just

fun loadKoinModules(vararg modules: Module) {
    GlobalContext.get().modules(*modules)
}

Feels cleaner loading extra modules on the fly with loadKoinModules(...).
I've placed the call just after setContentView() - and so far no issue, works well. I Haven't done any lazy loading though as you suggested:

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_feature1)
        loadKoinModules(feature1Module)
// or     GlobalContext.get().modules(feature1Module)
    }

Also, I've verified that the single created in the main app module and used in my feature1Module has the same instance so all good!

@luislukas The lazy loading part is not that important. What's important is that the lazy delegate executes the loadKoinModules function only once.

The lazy loading part helps us hold off loading the modules until the fragment is actually created.

@haroldadmin Looking at what loadKoinModules(...) does and the code behind it, seems that it gets all the modules that are registered at the moment and adds the new one. That's fine, however, if for example you do loadKoinModules() in an activity or fragment, then go to another part of the app and again back, when invoking loadKoinModules() you get (understandably):

Caused by: org.koin.core.error.DefinitionOverrideException: Already existing definition or try to override an existing one: [type:Factory,class:'x.y.z.Feature1Class']

Root cause of this is this method in BeanRegistry class:

    private fun HashSet<BeanDefinition<*>>.addDefinition(definition: BeanDefinition<*>) {
        val added = add(definition)
        if (!added && !definition.options.override) {
            throw DefinitionOverrideException("Already existing definition or try to override an existing one: $definition")
        }
    }

Seems that a mechanism to keep track of what has been added is necessary in order to avoid the exception, or somehow, play with different gradle flavours and load all the definitions at the same time.
I definitely see value in a check that won't add the module if it's already added - that will make life for devs easier. Could this be a feature request?

cc: @arnaudgiuliani

It would also be great if we could get official documentation on how to use Koin in dynamic feature modules with fragments/activities.

yeah loadKoinModules add new definitions from the given modules. Better is to consider using override option on your module to be sure to override a reloaded definition

There can be a good article about dynamic modules architecture :)

I'm running into the same issue. Would be great if we can either:

1: Have a method to unload modules loaded via loadKoinModules (probably call this at Activity#onDestory when isFinishing or ViewModel#onCleared

2: Choose to not throw an exception when a module definition is found, and we're calling loadKoinModules

@arnaudgiuliani The internal check to not override existing modules is perfect, though a boolean to be able to ignore the exception would be great.

In a multi-module dynamic Android App, I could do this in my Activity:
loadKoinModules(ignoreDefinitionExists = true)

Right now, the problem is that a component outside Koin doesn't know if it's module definitions have been loaded into Koin, and no way to check if it fails other than wrapping in a try-catch.

I'd love to pick this feature up if up for contribution! Doesn't seem like a major change.

Choosing not to throw an exception would be really useful since I have the feeling that the trick of override modules will load again the dependencies so +1 to @CalvinNor suggestion.

an unloadKoinModules() function that would unload given definitions instances?

here is the usecase for unloadKoinModules, and to help us achieve complete dynamic modules handling:

val module = module {
    single { (id: Int) -> Simple.MySingle(id) }
}
startKoin {
    modules(module)
}

// first instance
assertEquals(42, GlobalContext.get().koin.get<Simple.MySingle> { parametersOf(42) }.id)

// drop definition & instance
unloadKoinModules(module)
// reload it
loadKoinModules(module)

// new instance
assertEquals(24, GlobalContext.get().koin.get<Simple.MySingle> { parametersOf(24) }.id)

added to Koin 2.0.0-GA3

@arnaudgiuliani I am trying to using loadKoinModules() in a signed APK and it doesn't seem to be working. If i build a debug APK, i am able to override modules. Is there any specific setting which needs to be set to make it work with signed APKs?

class MainActivity : AppCompatActivity() {

    val dynamicFeatures by lazy { loadKoinModules(dynamicHttpModule) }
    fun overrideKoinModules() = dynamicFeatures

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
       unloadKoinModules(httpModules)
        overrideKoinModules()
}
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

haroldadmin picture haroldadmin  路  3Comments

miladsalimiiii picture miladsalimiiii  路  3Comments

erikhuizinga picture erikhuizinga  路  3Comments

sankarsana picture sankarsana  路  4Comments

iRYO400 picture iRYO400  路  3Comments