Koin: Inject of bean from a different context

Created on 22 Jun 2018  路  8Comments  路  Source: InsertKoinIO/koin

Describe the bug
We tried to switch to Koin from Dagger but we run into some strange behavior with the context feature.
If we create an object which loads a module inside a class which gets injected we are getting a scope error.

I've created a super small project like the following:

The MainActivity starting Koin and injecting an class Sdk

class MainActivity : AppCompatActivity() {
    val sdk: Sdk by inject()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        startKoin(listOf(AppModule().module))
        sdk.toString()
    }
}

The AppModule only has one bean for the Sdk

class AppModule {

    val module = applicationContext {
        bean { Sdk() }
    }
}

The Sdk class itself contains a Backend object (no injection, only the object)

class Sdk {
    val backend: Backend = Backend()

    init {
        backend.toString()
    }
}

The Backend-class itself is a KoinComponent loading a BackendModule and injecting a class called Bar. Consider this a library with its oun context.

class Backend : KoinComponent {

    val bar: Bar by inject()

    init {
        loadKoinModules(listOf(BackendModule().module))
        bar.toString()
    }
}

BackendModule includes a bean inside a context with name "backend"

class BackendModule {

    val module = applicationContext {
        context("backend") {
            bean { Bar() }
        }
    }
}

The line in the Sdk class where the Backend gets created is crashing with the following stacktrace:

06-22 12:09:13.092 30620-30620/com.example.mirko.testcontextrelease W/System.err: org.koin.error.ContextVisibilityException: Can't resolve 'com.example.mirko.testcontextrelease.Bar' for definition Bean[class=com.example.mirko.testcontextrelease.Sdk].
        Class 'com.example.mirko.testcontextrelease.Bar' is not visible from context scope Scope[ROOT]
        at org.koin.KoinContext.getVisibleBeanDefinition(KoinContext.kt:111)
        at org.koin.KoinContext.resolveInstance(KoinContext.kt:77)
        at com.example.mirko.testcontextrelease.Backend$$special$$inlined$inject$1.invoke(KoinComponent.kt:114)
        at kotlin.SynchronizedLazyImpl.getValue(Lazy.kt:131)
        at com.example.mirko.testcontextrelease.Backend.getBar(Unknown Source:7)
        at com.example.mirko.testcontextrelease.Backend.<init>(Backend.kt:14)
        at com.example.mirko.testcontextrelease.Sdk.<init>(Sdk.kt:4)
        at com.example.mirko.testcontextrelease.AppModule$module$1$1.invoke(AppModule.kt:8)
        at com.example.mirko.testcontextrelease.AppModule$module$1$1.invoke(AppModule.kt:5)
06-22 12:09:13.093 30620-30620/com.example.mirko.testcontextrelease W/System.err:     at org.koin.core.instance.InstanceFactory.createInstance(InstanceFactory.kt:58)
        at org.koin.core.instance.InstanceFactory.retrieveInstance(InstanceFactory.kt:26)
        at org.koin.KoinContext$resolveInstance$$inlined$synchronized$lambda$1.invoke(KoinContext.kt:85)
        at org.koin.KoinContext$resolveInstance$$inlined$synchronized$lambda$1.invoke(KoinContext.kt:23)
        at org.koin.ResolutionStack.resolve(ResolutionStack.kt:23)
        at org.koin.KoinContext.resolveInstance(KoinContext.kt:80)
        at com.example.mirko.testcontextrelease.MainActivity$$special$$inlined$inject$1.invoke(AndroidExt.kt:179)
        at kotlin.SynchronizedLazyImpl.getValue(Lazy.kt:131)
        at com.example.mirko.testcontextrelease.MainActivity.getSdk(Unknown Source:7)
06-22 12:09:13.094 30620-30620/com.example.mirko.testcontextrelease W/System.err:     at com.example.mirko.testcontextrelease.MainActivity.onCreate(MainActivity.kt:19)
        at android.app.Activity.performCreate(Activity.java:7009)
        at android.app.Activity.performCreate(Activity.java:7000)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1214)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2731)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2856)
        at android.app.ActivityThread.-wrap11(Unknown Source:0)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1589)
        at android.os.Handler.dispatchMessage(Handler.java:106)
06-22 12:09:13.095 30620-30620/com.example.mirko.testcontextrelease W/System.err:     at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6494)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
06-22 12:09:13.096 30620-30620/com.example.mirko.testcontextrelease D/AndroidRuntime: Shutting down VM
06-22 12:09:13.098 30620-30620/com.example.mirko.testcontextrelease E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.mirko.testcontextrelease, PID: 30620
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.mirko.testcontextrelease/com.example.mirko.testcontextrelease.MainActivity}: org.koin.error.BeanInstanceCreationException: Can't create bean Bean[class=com.example.mirko.testcontextrelease.Sdk] due to error :
        org.koin.error.ContextVisibilityException: Can't resolve 'com.example.mirko.testcontextrelease.Bar' for definition Bean[class=com.example.mirko.testcontextrelease.Sdk].
        Class 'com.example.mirko.testcontextrelease.Bar' is not visible from context scope Scope[ROOT]
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2778)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2856)
        at android.app.ActivityThread.-wrap11(Unknown Source:0)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1589)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6494)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
     Caused by: org.koin.error.BeanInstanceCreationException: Can't create bean Bean[class=com.example.mirko.testcontextrelease.Sdk] due to error :
        org.koin.error.ContextVisibilityException: **Can't resolve 'com.example.mirko.testcontextrelease.Bar' for definition Bean[class=com.example.mirko.testcontextrelease.Sdk].
        Class 'com.example.mirko.testcontextrelease.Bar' is not visible from context scope Scope[ROOT]**
        at org.koin.core.instance.InstanceFactory.createInstance(InstanceFactory.kt:63)
        at org.koin.core.instance.InstanceFactory.retrieveInstance(InstanceFactory.kt:26)
        at org.koin.KoinContext$resolveInstance$$inlined$synchronized$lambda$1.invoke(KoinContext.kt:85)
        at org.koin.KoinContext$resolveInstance$$inlined$synchronized$lambda$1.invoke(KoinContext.kt:23)
        at org.koin.ResolutionStack.resolve(ResolutionStack.kt:23)
        at org.koin.KoinContext.resolveInstance(KoinContext.kt:80)
        at com.example.mirko.testcontextrelease.MainActivity$$special$$inlined$inject$1.invoke(AndroidExt.kt:179)
        at kotlin.SynchronizedLazyImpl.getValue(Lazy.kt:131)
        at com.example.mirko.testcontextrelease.MainActivity.getSdk(Unknown Source:7)
        at com.example.mirko.testcontextrelease.MainActivity.onCreate(MainActivity.kt:19)
        at android.app.Activity.performCreate(Activity.java:7009)
        at android.app.Activity.performCreate(Activity.java:7000)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1214)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2731)
            ... 9 more

It can not resolve Bar in Backend of scope ROOT - but this seems to be wrong.

question

Most helpful comment

Things should be clearer in 1.0.0. Wait the next weeks for new documentation 馃憤

All 8 comments

Maybe i can help you:
You have two modules: AppModule and BackendModule

The Sdk class is provided in the AppModule with root context. The Backend class which you want to inject in the Sdk class needs a Bar class which is provided in the BackendModule in another context "backend".

So the usage of context is the following:

root: knows only classes/interfaces/... from the root context
backend: knows classes from the root context AND the backend context

So your problem is that the Sdk class from the root context needs injects a Backend class which needs a class (Bar) from the backend context. And this is not visible in the root context. See the error message:

Can't resolve 'com.example.mirko.testcontextrelease.Bar' for definition Bean[class=com.example.mirko.testcontextrelease.Sdk].
        Class 'com.example.mirko.testcontextrelease.Bar' is not visible from context scope Scope[ROOT]

Yes but it was not clear how to fix that. I found the solution yesterday already.
I put the Sdk in the AppModule also into a context called backend like this:

val module = applicationContext {
    context("backend"){
        bean { Sdk() }
    }
}

Then everything is working. Actually the Backend object in the Sdk is not injected, only created. The backend class itself is injecting the Bar. Only the Backend knows stuff from the context "backend", the Sdk just holds a reference to the Bakend. Its kind of confusing I think that the Sdk then also has to be inside the context.

Took me a while to figure that out but now it works fine. I also was looking for some kid of an inject(with context). I especially need that to release the context at some point.

Things should be clearer in 1.0.0. Wait the next weeks for new documentation 馃憤

This issue helped me but it seems counter intuitive from an API perspective. Maybe it's just documentation as @arnaudgiuliani mentioned, but let me expose the issue from my perspective.

  • I'm creating a library, available on jcenter() and lots of other developers will be using it.
  • The correct usage of this library is to have it as a singleton (bean) for the application.
  • My library is all written in Kotlin and uses Koin throughout.

That above seems a pretty common use case, but as far as I can see on this ticket, the only "fix" to this crash is to add my library context to the application module.

That means somewhere on my library documentation there will be an extra section explaining that if the application is also using Koin, it has to wrap my library in this other context. And even thou it's just 1 extra line of code, it certainly seems a bit counter intuitive.

Specially on the library cases, it should be possible to have it's own separate instance of root context for its own injection, else it might be creating unintentional side effects.

Well, just my 2 cents.
Still thanks for the all the work, it's looking good and I really like how it completely dismisses code generation.

I know this is still tedious, but with Koin 1.0 I hope it will be modules will be clearer.

For library makers who want to use Koin, the best thing is to declare all your stuff in modules and not in the root:

val sdkModule = applicationContext{
     context("org.my.sdk"){
         // Declare all your definitions here
     }
}

By using a namespace for all your definition, you won't have any conflict with people using your lib and Koin for their app. Because all your definitions are confined in `"org.my.sdk" space.

We have good feedback on making sdk/app with Koin, but we need to provide some guidelines.

For library makers who want to use Koin, the best thing is to declare all your stuff in modules and not in the root:

but thats the problem. I don't want to have everything in the same context because I need to release the context of the backend (or the library) at some point in the code. Therefor I need different contexts for the libraries.

@Informaticore you can have modules/submodules and then make a module for instances that will be dropped later.

in Koin 1.0:

val sdk = module("org.your.sdk"){
    // Your stuff here
    module ("session"){
       // session stuff here - to be dropped later 
   }
}

and when you need: release("org.your.sdk.session") will drop the org.your.sdk.session sub module

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Jeevuz picture Jeevuz  路  4Comments

ILAgent picture ILAgent  路  3Comments

mubarak1361 picture mubarak1361  路  3Comments

sankarsana picture sankarsana  路  4Comments

caleb-allen picture caleb-allen  路  4Comments