Koin: Does Koin work with Android BroadcastReceiver in the manifest?

Created on 21 Jun 2019  路  6Comments  路  Source: InsertKoinIO/koin

Describe the bug
I have an Android app which uses Koin and which uses Robolectric for some unit tests.
All unit tests that use Robolectric fail with the stack below.
This appears to be because I have a BroadcastReceiver declared in the manifest
<receiver android:name="MyBroadcastReceiver"/>
And MyBroadcastReceiver implements KoinComponent and uses by inject.

It appears that because the BroadcastReceiver is declared in the manifest it gets created BEFORE my App.onCreate (which is where I start Koin). When I remove the BroadcastReceiver from the manifest the tests pass.

Am I doing something wrong? Possibly this is a Robolectric bug instead? I am migrating an app from Dagger to Koin and these unit test previously worked fine with Dagger.

java.lang.IllegalStateException: KoinApplication has not been started

    at org.koin.core.context.GlobalContext.get(GlobalContext.kt:37)
    at org.koin.core.KoinComponent$DefaultImpls.getKoin(KoinComponent.kt:32)
    at com.metaswitch.max.util.MyBroadcastReceiver.getKoin(MyBroadcastReceiver.kt:52)
    at com.metaswitch.max.util.MyBroadcastReceiver.<init>(MyBroadcastReceiver.kt:143)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at org.robolectric.util.ReflectionHelpers.callConstructor(ReflectionHelpers.java:379)
    at org.robolectric.internal.bytecode.ShadowImpl.newInstanceOf(ShadowImpl.java:18)
    at org.robolectric.shadow.api.Shadow.newInstanceOf(Shadow.java:35)
    at org.robolectric.android.internal.AndroidEnvironment.registerBroadcastReceivers(AndroidEnvironment.java:491)
    at org.robolectric.android.internal.AndroidEnvironment.installAndCreateApplication(AndroidEnvironment.java:242)
    at org.robolectric.android.internal.AndroidEnvironment.setUpApplicationState(AndroidEnvironment.java:149)
    at org.robolectric.RobolectricTestRunner.beforeTest(RobolectricTestRunner.java:298)
    at org.robolectric.internal.SandboxTestRunner$2.lambda$evaluate$0(SandboxTestRunner.java:247)
    at org.robolectric.internal.bytecode.Sandbox.lambda$runOnMainThread$0(Sandbox.java:89)
    at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
    at java.util.concurrent.FutureTask.run(FutureTask.java)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)

To Reproduce
See above.

Expected behavior
Unit test to pass.

Koin project used and used version (please complete the following information):
"org.koin:koin-androidx-viewmodel:2.0.1"
"org.koin:koin-test:2.0.1"

question

Most helpful comment

I think I see how to workaround this.

Split my BroadcastReceiver into two classes:

  • An actual BroadcastReceiver.
  • A Helper class that has the Koin injection and does all the work.
class MyBroadcastReceiver {
    private val myhelper: MyHelper by lazy { MyHelper() }

    override fun onReceive(context: Context, intent: Intent) {
        myhelper.onReceive(context, Intent)
    }
}

class MyHelper : KoinComponent {
    private val foo: Foo by inject()

    fun onReceive(context: Context, intent: Intent) {
        // Do the work here that used to be in the BroadcastReceiver.
    }

Now MyHelper isn't created until Koin has been initialised in App.onCreate.

All 6 comments

I know there is a duplicate of this 'issue' but I cannot find it....

Indeed BoradcastReceivers declared in the AndroidManifest get created before your Application onCreate. That is by design. If you use BroadcastReceivers that rely on Koin, then you need to initiale Koin already there. Really this is not Koin specific but jsut an Android quirck. It also applies to a normal appication run, by the way, not just Roboelectric tests.

Thanks, that's very helpful.

I can workaround this easily in my unit tests by disabling the BroadcastReceivers (I never intended for them to run during Robolectric tests anyway). But in the actual app, does this mean that I either can't use Koin from within a BroadcastReceiver or do I have to call startKoin from the BroadcastReceivers as well as from App.onCreate ?

Personally I would create a dedicated BroadcastReceiver that is responsible for the app init, including starting Koin.

I am running into the same problem, can you elaborate on that @erickok? Is there a mechanism to have a global BroadcastReceiver or would I need to have a receiver that has a filter on all intents that other receivers are filtering on?
The other receivers are not under my control (e.g. the androidx.work receiver).

Also, how do I avoid starting koin multiple times, if also the init in the Application has taken place?

I think I see how to workaround this.

Split my BroadcastReceiver into two classes:

  • An actual BroadcastReceiver.
  • A Helper class that has the Koin injection and does all the work.
class MyBroadcastReceiver {
    private val myhelper: MyHelper by lazy { MyHelper() }

    override fun onReceive(context: Context, intent: Intent) {
        myhelper.onReceive(context, Intent)
    }
}

class MyHelper : KoinComponent {
    private val foo: Foo by inject()

    fun onReceive(context: Context, intent: Intent) {
        // Do the work here that used to be in the BroadcastReceiver.
    }

Now MyHelper isn't created until Koin has been initialised in App.onCreate.

Yes, use KoinComponent where the current base extensions are not present.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

AHarazim picture AHarazim  路  3Comments

sankarsana picture sankarsana  路  4Comments

CristianMG picture CristianMG  路  3Comments

guymclean picture guymclean  路  3Comments

erikhuizinga picture erikhuizinga  路  3Comments