InsertKoinIO / koin

Koin - a pragmatic lightweight dependency injection framework for Kotlin & Kotlin Multiplatform
https://insert-koin.io
Apache License 2.0
9.08k stars 719 forks source link

NoBeanDefFoundException in WorkManager #1729

Open smuldr opened 11 months ago

smuldr commented 11 months ago

Describe the bug We get occasional crashes when using koin-androidx-workmanager to inject our worker classes. It seems like this is a continuation of #1623.

Fatal Exception: java.lang.Error: org.koin.core.error.InstanceCreationException: Could not create instance for '[Factory:'my.package.SomeWorker',qualifier:q:'my.package.SomeWorker',binds:androidx.work.ListenableWorker]'
       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1173)
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
       at java.lang.Thread.run(Thread.java:920)
Caused by org.koin.core.error.InstanceCreationException: Could not create instance for '[Factory:'my.package.SomeWorker',qualifier:q:'my.package.SomeWorker',binds:androidx.work.ListenableWorker]'
       at org.koin.core.instance.InstanceFactory.create(InstanceFactory.kt:57)
       at org.koin.core.instance.FactoryInstanceFactory.get(FactoryInstanceFactory.kt:38)
       at org.koin.core.registry.InstanceRegistry.resolveInstance$koin_core(InstanceRegistry.kt:116)
       at org.koin.core.scope.Scope.resolveValue(Scope.kt:247)
       at org.koin.core.scope.Scope.resolveInstance(Scope.kt:233)
       at org.koin.core.scope.Scope.get(Scope.kt:212)
       at org.koin.core.scope.Scope.getOrNull(Scope.kt:177)
       at org.koin.core.scope.Scope.getOrNull$default(Scope.kt:160)
       at org.koin.androidx.workmanager.factory.KoinWorkerFactory.createWorker(KoinWorkerFactory.kt:41)
       at androidx.work.DelegatingWorkerFactory.createWorker(DelegatingWorkerFactory.java:71)
       at androidx.work.WorkerFactory.createWorkerWithDefaultFallback(WorkerFactory.java:83)
       at androidx.work.impl.WorkerWrapper.runWorker(WorkerWrapper.java:243)
       at androidx.work.impl.WorkerWrapper.run(WorkerWrapper.java:145)
       at androidx.work.impl.utils.SerialExecutorImpl$Task.run(SerialExecutorImpl.java:96)
       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
       at java.lang.Thread.run(Thread.java:920)
Caused by org.koin.core.error.NoBeanDefFoundException: No definition found for type 'my.package.SomeWorker'. Check your Modules configuration and add missing type and/or qualifier!
       at org.koin.core.scope.Scope.throwDefinitionNotFound(Scope.kt:301)
       at org.koin.core.scope.Scope.resolveValue(Scope.kt:271)
       at org.koin.core.scope.Scope.resolveInstance(Scope.kt:233)
       at org.koin.core.scope.Scope.get(Scope.kt:212)
       at org.koin.core.scope.Scope.get$default(Scope.kt:136)
       at my.package.MyWorkerModuleKt$workersModule$1$11.invoke(MyWorkerModule.kt:51)
       at my.package.MyWorkerModuleKt$workersModule$1$11.invoke(MyWorkerModule.kt:47)
       at org.koin.core.instance.InstanceFactory.create(InstanceFactory.kt:50)
       at org.koin.core.instance.FactoryInstanceFactory.get(FactoryInstanceFactory.kt:38)
       at org.koin.core.registry.InstanceRegistry.resolveInstance$koin_core(InstanceRegistry.kt:116)
       at org.koin.core.scope.Scope.resolveValue(Scope.kt:247)
       at org.koin.core.scope.Scope.resolveInstance(Scope.kt:233)
       at org.koin.core.scope.Scope.get(Scope.kt:212)
       at org.koin.core.scope.Scope.getOrNull(Scope.kt:177)
       at org.koin.core.scope.Scope.getOrNull$default(Scope.kt:160)
       at org.koin.androidx.workmanager.factory.KoinWorkerFactory.createWorker(KoinWorkerFactory.kt:41)
       at androidx.work.DelegatingWorkerFactory.createWorker(DelegatingWorkerFactory.java:71)
       at androidx.work.WorkerFactory.createWorkerWithDefaultFallback(WorkerFactory.java:83)
       at androidx.work.impl.WorkerWrapper.runWorker(WorkerWrapper.java:243)
       at androidx.work.impl.WorkerWrapper.run(WorkerWrapper.java:145)
       at androidx.work.impl.utils.SerialExecutorImpl$Task.run(SerialExecutorImpl.java:96)
       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
       at java.lang.Thread.run(Thread.java:920)

To Reproduce

Our flow is as follows:

  1. We removed the default WorkManager initializer from the AndroidManifest
  2. We use an Initializer from the AndroidX Startup component to start Koin and set up the Koin WorkManager factory.
  3. There is a second Initializer that enqueues a periodic WorkRequest in WorkManager. This worker initializer depends on our Koin initializer so Koin should be initialized.
  4. The app also enqueues some one time work when the first activity is created.

Both the periodic and the one time workers produce NoBeanDefFoundEception crashes. It only happens to a small percentage of users, but it still happens with every release we make.

Koin module and version:

svob commented 10 months ago

Hi, I have the same issue. Is there any solution or workaround for this?

smuldr commented 10 months ago

We ended up removing the koin-androidx-workmanager library completely and made our workers KoinComponents so they can get or inject their dependencies when they actually do the work.

class MyWorker(
  appContext: Context,
  params: WorkerParameters,
) : Worker(appContext, params),
  KoinComponent {

  // Use `by inject()` to get a lazy dependency.
  private val myDependency: SomeClass by inject()

  override suspend fun doWork(): Result {
    // Only access the dependency in `doWork()`
    myDependency.doSomething()
    TODO("Return a result")
  }
}

This makes the class more difficult to test but at least the crashes went away. I guess that this approach gives Koin a little more time to get the dependency graph ready.

arnaudgiuliani commented 9 months ago

Can you check how I setup it in NowInAndroid, and compare with yours? https://github.com/InsertKoinIO/nowinandroid

smuldr commented 9 months ago

That is a big project to quickly check for the setup! 😄 Are you talking about the way you manually trigger the SyncWorker in the app's Application right here?

I am afraid that we have too much code relying on having Koin initialized via the AndroidX Startup library to quickly make this change. I should really put together a demo project to help figure out the cause of this crash rather than try to find a workaround.

arnaudgiuliani commented 9 months ago

Seems that worker is not well defined. But it needs deeper investigation on your side 🤔

ChrisKruegerDev commented 4 weeks ago

Even in small workers and apps, this issue can happen:

@KoinWorker
class ScheduleRemindersWorker(
    context: Context,
    params: WorkerParameters,
    private val reminderRepository: RemindersRepository
) : TaskCoroutineWorker(context, params) {

    override suspend fun doTask() {
        reminderRepository.scheduleReminders()
    }
}

// execution
      workManager.enqueueUniqueWork(
            ScheduleRemindersWorker.NAME_STARTUP,
            ExistingWorkPolicy.KEEP,
            OneTimeWorkRequest.from(ScheduleRemindersWorker::class.java),
        )

Stack trace:

 Fatal Exception: org.koin.core.error.InstanceCreationException: Could not create instance for '[Factory: 'app.resubs.data.reminder.ScheduleRemindersWorker',qualifier:app.resubs.data.reminder.ScheduleRemindersWorker,binds:androidx.work.c]'
       at org.koin.core.instance.InstanceFactory.create(InstanceFactory.kt:58)
       at org.koin.core.instance.FactoryInstanceFactory.get(FactoryInstanceFactory.kt:38)
       at org.koin.core.registry.InstanceRegistry.resolveInstance$koin_core(InstanceRegistry.kt:110)
       at org.koin.core.scope.Scope.resolveFromRegistry(Scope.kt:321)
       at org.koin.core.scope.Scope.resolveFromContext(Scope.kt:311)
       at org.koin.core.scope.Scope.stackParametersCall(Scope.kt:281)
       at org.koin.core.scope.Scope.resolveInstance(Scope.kt:259)
       at org.koin.core.scope.Scope.resolveWithOptionalLogging(Scope.kt:232)
       at org.koin.core.scope.Scope.get(Scope.kt:215)
       at org.koin.core.scope.Scope.getOrNull(Scope.kt:178)
       at org.koin.androidx.workmanager.factory.KoinWorkerFactory.createWorker(KoinWorkerFactory.kt:47)
       at androidx.work.DelegatingWorkerFactory.createWorker(DelegatingWorkerFactory.java:71)
       at androidx.work.WorkerFactory.createWorkerWithDefaultFallback(WorkerFactory.java)
       at androidx.work.impl.WorkerWrapper.runWorker(WorkerWrapper.java:243)
       at androidx.work.impl.WorkerWrapper.run(WorkerWrapper.java:144)
       at androidx.work.impl.utils.SerialExecutorImpl$Task.run(SerialExecutorImpl.java:96)
       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
       at java.lang.Thread.run(Thread.java:1012)

Questions to look into: