Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use Koin in multiple module?

There are two modules in my android project, app module and lib module.

Both these two modules need Koin for D.I., so I call startKoin in MyApplication class in app module, and IninKointContentProvider in lib module as below.

// app module
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin(this, modules1)
    }
}

// lib module
class InitKoinContentProvider : ContentProvider() {
    override fun onCreate(): Boolean {
        startKoin(context.applicationContext, modules2)
        return true
    }
}

Then app crashed and shown this message

Caused by: org.koin.error.BeanOverrideException: Try to override definition with Single [class='android.content.Context'], but override is not allowed. Use 'override' option in your definition or module.

I guess startKoin can be called only one time.

The solution I found is merging two koin modules then calling startKoin in MyApplication, but I don't like it. Lib module may be imported by other android project which doesn't use koin, in that case, I think calling startKoin in InitKoinContentProvider is better.

Any solution for this problem?? Thanks!

like image 904
aiueoH Avatar asked May 24 '19 09:05

aiueoH


4 Answers

I found the best solution inspired by @laalto's answer, thanks!

Upgrade to koin 2.0, then use KoinApplication and customized KoinComponent to create a isolated koin context, it can let lib module using koin without any initializing call by app module, still start koin in ContentProvider. The whole code may like below.

// app module
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin {
            androidContext(this@MyApplication)
            modules(module{
                viewModel { MainViewModel() }
            })
        }
    }
}

class MainActivity: AppCompactActivity() {
    private val viewModel: MainViewModel by viewModel()
}



// lib module
internal object MyKoinContext {
    lateinit var koinApplication: KoinApplication
}

interface MyKoinComponent : KoinComponent {
    override fun getKoin(): Koin {
        return MyKoinContext.koinApplication.koin
    }
}

class InitKoinContentProvider : ContentProvider() {
    override fun onCreate(): Boolean {
        MyKoinContext.koinApplication = koinApplication {
            androidContext(context.applicationContext)
            modules(module{
                viewModel { FooViewModel() }
            })
        }
        return true
    }
}

class FooActivity: AppCompactActivity(), MyKoinComponent {
    private val viewModel: FooViewModel by viewModel()
}

Ref: https://insert-koin.io/docs/2.0/documentation/reference/index.html#_koin_context_isolation

like image 96
aiueoH Avatar answered Oct 26 '22 23:10

aiueoH


In your library modules, use loadKoinModules() to load the module-specific koin modules. Docs.

You need to have run startKoin() prior to that, so the init order with content providers can be a little tricky.

like image 40
laalto Avatar answered Oct 27 '22 01:10

laalto


To init extra koin modules on other project modules and get no duplicate loading issues (e.g. pressing home, than coming back to the activity), go to your module declaration file:

val myModule = module {
    single { MyRepository(get()) }
    viewModel { MyViewModel(get()) }
}

private val loadKoinModules by lazy {
    loadKoinModules(myModule)
}

fun inject() = loadKoinModules

Then on your view:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    inject()
}
like image 30
gusgol Avatar answered Oct 27 '22 00:10

gusgol


TL;DR Use single/factory methods with override param set to true when providing your dependencies that are overriding those provided by the modules loaded before.

single<Manager>(override = true) { TestManager() }

I have faced a similar issue when I tried to override one of the dependencies for UI test purposes. When I setup in Application.onCreate():

startKoin {
   module {
       single { Printer() }
   }
}

and then in before method of test:

loadKoinModules(module {
    single<Printer> { TestPrinter() }
})

I get a Runtime exception during the test: org.koin.core.error.DefinitionOverrideException: Already existing definition or try to override an existing one

And the solution is to show Koin that you are intentionally overriding that dependency by using override param of single function like that:

loadKoinModules(module {
    single<Printer>(override = true) { TestPrinter() }
})
like image 22
Andrew Panasiuk Avatar answered Oct 27 '22 00:10

Andrew Panasiuk