Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add modules dynamically to the graph #419

Closed
luislukas opened this issue Apr 5, 2019 · 14 comments
Closed

Add modules dynamically to the graph #419

luislukas opened this issue Apr 5, 2019 · 14 comments
Labels
Milestone

Comments

@luislukas
Copy link

luislukas commented Apr 5, 2019

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!

@luislukas
Copy link
Author

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

@haroldadmin
Copy link

haroldadmin commented Apr 8, 2019

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: #420

@luislukas
Copy link
Author

luislukas commented Apr 8, 2019

@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!

@haroldadmin
Copy link

@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.

@luislukas
Copy link
Author

luislukas commented Apr 9, 2019

@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

@haroldadmin
Copy link

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

@arnaudgiuliani
Copy link
Member

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 :)

@calvinnor
Copy link

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.

@luislukas
Copy link
Author

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.

@arnaudgiuliani
Copy link
Member

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

@arnaudgiuliani
Copy link
Member

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)

@arnaudgiuliani arnaudgiuliani added this to the 2.0.0 milestone May 15, 2019
@arnaudgiuliani
Copy link
Member

added to Koin 2.0.0-GA3

@pkondamudi
Copy link

pkondamudi commented Apr 28, 2020

@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()
}
}

@vpuonti
Copy link

vpuonti commented Feb 3, 2021

I've used by lazy { loadKoinModules(feature1Modules) } to load Dynamic Feature's modules to the graph, but now I'm facing an issue testing the Activity that injects the modules.

I'd like to load my mocked test modules instead of loading the whole feature's modules.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants