15

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!

Phantômaxx
  • 37,901
  • 21
  • 84
  • 115
aiueoH
  • 728
  • 1
  • 8
  • 24

6 Answers6

7

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

aiueoH
  • 728
  • 1
  • 8
  • 24
  • Crash caused by: kotlin.UninitializedPropertyAccessException: lateinit property koinApplication has not been initialized in koin 2.1.6 – mrahimygk Jul 14 '20 at 09:23
  • @aiueoH how do I start the InitKoinContentProvider? I'm not very familiarized with Content Providers – Leonardo Sibela Oct 26 '22 at 15:10
  • Sry for asking after many years. May I ask how you set up the unit test for MyKoinComponent? In my case, I followed the documentation but injections are still coming from MyKoinComponent. – Hein Htet Aung Jun 08 '23 at 05:35
5

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.

Mark
  • 7,446
  • 5
  • 55
  • 75
laalto
  • 150,114
  • 66
  • 286
  • 303
4

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()
}
gusgol
  • 483
  • 5
  • 11
4

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() }
})
Andrew Panasiuk
  • 626
  • 1
  • 8
  • 17
2

By design startKoin is meant to be called from the Application class. You can provide a parameter in the lib whether to call startKoin or not. But I doubt that including such things as Koin in libs is a good practice. What if an application already includes Koin, but of different version?

Anton Malyshev
  • 8,686
  • 2
  • 27
  • 45
0

This way worked perfect for me:

@KoinExperimentalAPI 
class MainApplication : Application() {

    override fun onCreate() {
        super.onCreate()
        initKoin()
    }

    private fun initKoin() {
        startKoin {
            androidLogger()
            androidContext(this@MainApplication)
        }
        loadKoinModules(
            myFeatureModule
        )
    }
}

And define you module in your feature as usual:

val myFeatureModule = module {
    factory<...> { ...() }

    single { ...() }

    viewModel { ...(get() }
}
Andy
  • 751
  • 1
  • 12
  • 25