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

[question] Best way to isolate Koin modules/singles when building an Android library module? #224

Closed
AidanLaing opened this issue Sep 12, 2018 · 10 comments
Labels
core status:checking currently in analysis - discussion or need more detailed specs
Milestone

Comments

@AidanLaing
Copy link

Currently in my project I have an android app module and an android library module for an SDK I'm building.

Both Android modules are using Koin. The app module is using koin-android-viewmodel and the SDK module is using koin-core with its own KoinComponent.

I'm running into issues since both the app and SDK have a dependency on a Koin single OkHttpClient. After calling loadKoinModules() It looks like these two Android modules end up sharing the same pool of Koin singles and it crashes because it doesn't know which OkHttpClient to pick when I call get().

I've got it working by using the name parameter for both OkHttpClient Koin singles and passing it in the get('name') whenever I want the OkHttpClient in either Android Module. I don't want to have to force my SDK user to have to use these named params if they are using Koin with a OkHttpClient as well.

I would prefer if I could just reduce the visibility of my OkHttpClient in my SDK to only be visible inside the SDK module and also so it can not see the other OkHttpClient in my app module.

Is there a better way to do this than using the single name param?

Is it possible to have two Koin instances using startKoin() instead of loadKoinModules() without crashing?

Thanks :)

@budius
Copy link

budius commented Sep 17, 2018

+1 for this question.

I'm running in the same issue and currently I'm having to put name parameters on everything that client app might be using such as all Android framework classes and common libraries stuff such as OkHttp / Retrofit.

Furthermore I would like to expand a bit the question regarding the namespaces for the modules.
As an attempt to separate a bit the visibility, I tried adding package name as namepsace for my library, but then I ran into the issue that the app must declare our library inside the same namespace else -> crash. While ideally we would be able to separate the library without the client app having to know about our Koin usage.

Quick example of what I mean is:

val myLibraryModule = module("com.our.company.package.lib.name") {
   .... declarations...
}

and then if the app tries to declare the

val appModule = module {
   single { awesomeLibrary(context) }
}

it crashes with:

Error while resolving instance for class 'com.ourcompany.awesome.library' - error: org.koin.error.BeanInstanceCreationException: Can't create definition for 'Single [name='library',class='com.ourcompany.awesome.library']' due to error :
    		Can't proceedResolution 'class com.ourcompany.awesome.library.Foo (Kotlin reflection is not available)' - Definition is not visible from last definition : Single [name='library',class='com.ourcompany.awesome.library']
    		org.koin.core.bean.BeanRegistry.retrieveDefinition(BeanRegistry.kt:100)
    		org.koin.core.instance.InstanceRegistry$proceedResolution$$inlined$synchronized$lambda$1.invoke(InstanceRegistry.kt:87)

@arnaudgiuliani arnaudgiuliani added core status:checking currently in analysis - discussion or need more detailed specs labels Sep 19, 2018
@arnaudgiuliani
Copy link
Member

You can declare several modules with the same name, to share the same namespace. For example:

val myLibraryModule = module("com.our.company.package.lib.name") {
   .... declarations...
}
val appModule = module("com.our.company.package.lib.name") {
   single { awesomeLibrary(context) }
}

second thing is to request dependencies via their names, to avoid request it globally: by inject(name="...") - Koin gives a default name to your component.

This is for your SDK. But it won't avoid your underlying app resolve against your definitions. Isolation should be better. :/

@arnaudgiuliani
Copy link
Member

Best way to isolate your use of Koin, is doing it with a dedicated Koin instance. You have to make it more "manually":

val myKoinKontext  = Koin.create().loadModules(listOf(myModules...)).koinContext

From your KoinContext, you can use any feature. And you can reuse it in a custom component, using KoinComponent:

abstract class CustomKoinComponent : KoinComponent {
        val manualContext: KoinContext by lazy {
            Koin.create().loadModules(listOf(myModules)).koinContext
        }

        override fun getKoin(): KoinContext = manualContext
    }

API has been enhanced in 1.0.2 to allow a clear isolation and reuse of Koin context.

@AlexTrotsenko
Copy link

AlexTrotsenko commented Dec 7, 2020

@arnaudgiuliani as far as I see, Koin.create() is not available in Koin 2 anymore.
How can we achieve the same isolate/separate Koin graph in the library so that it's not interfering with the Koin in the host app?

Should we use val koinApplication = koinApplication { } instead of Koin.create() and then override fun getKoin(): Koin = koinApplication.koin ?

@budius
Copy link

budius commented Mar 19, 2021

@AlexTrotsenko it's in the docs -> https://insert-koin.io/docs/reference/koin-core/start-koin#koin-context-isolation

@NicoloParolini
Copy link

NicoloParolini commented Sep 2, 2021

mmh docs can't even build. This line is totally incorrect:
override fun getKoin(): Koin = MyKoinContext?.koinApp.koin
getKoin() is not nullable but the static object var is and needs to be. The actual implementation becomes a lot more complex.

@jstarczewski
Copy link

jstarczewski commented Sep 26, 2021

@NicoloParolini
Yess, docs can't even build, but I managed to make it work for my Kotlin KMM application with following code:

  1. First define a Koin application holder for your module Koin instance
internal object SharedModuleKoinHolder {

    var koinApplication = koinApplication { }
}
  1. Define an abstract class with access to defined above Koin application instance
abstract class SharedKoinComponent : KoinComponent {

    override fun getKoin() = SharedModuleKoinHolder.koinApplication.koin
}

Finally override value of koinApplication in a palce where you setup your DI modules

internal object DiInitializer {

    internal fun setupDi(isDebug: Boolean) {
        SharedModuleKoinHolder.koinApplication = koinApplication {
            printLogger(getLoggerLevel(isDebug))
            modules(getModules(isDebug))
        }
    }
}

All your injections happen after you initialize your modules so it should work properly, then with use of given abstract implementing KoinComponent, instead of direct KoinComponent interface usage, you can access your bindings, for for example:

internal class KoinInteractorProvider : SharedKoinComponent() {

    internal inline fun <reified Type : Interactor> create(): Type = get()
}

It's all build according to the docs, expect the first part of init initialization with default empty koinApplication { }. One can add other mechanism preventing further reinitialization of var koinApplication, or use lateinit var etc. but it all depends on a given use case. Hope it helps :)
Maybe we can change the docs, that the given example here https://insert-koin.io/docs/reference/koin-core/start-koin#koin-context-isolation builds? @arnaudgiuliani

@mecoFarid
Copy link

@jstarczewski

Finally override value of koinApplication in a palce where you setup your DI modules

This part of your code does not even make any sense. I mean I wanna keep all library implementations in module where SharedModuleKoinHolder is located but in your case you just initialize it in DiInitializer which is like it is app level not a library level.

@WebTiger89
Copy link

WebTiger89 commented Feb 20, 2023

@mecoFarid Nah, DiInitializer is indeed meant to be in library. You just need a trigger to call setupDi(). That's how context isolation works according to the docs.

@arnaudgiuliani
Copy link
Member

the the idea is to keep your Koin instance dedicated to your SDK 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core status:checking currently in analysis - discussion or need more detailed specs
Projects
None yet
Development

No branches or pull requests

8 participants