Skip to content
This repository was archived by the owner on Aug 20, 2025. It is now read-only.
This repository was archived by the owner on Aug 20, 2025. It is now read-only.

Subcomponent + BindsInstance doesn't work #212

@Dkhusainov

Description

@Dkhusainov

I'm trying to use the library in an application that uses dagger to create Worker's for WorkManager(you can find many articles about it ).

Here's the code and a test case that mimics worker injection:

package foo

import dagger.*
import dagger.multibindings.IntoMap
import org.junit.Test
import javax.inject.Inject
import javax.inject.Provider
import javax.inject.Scope
import javax.inject.Singleton
import kotlin.reflect.KClass
import kotlin.test.assertEquals

//root component
@Component(
  modules = [
    WorkerFactoryModule::class,
    FeatureModule::class
  ]
)
@Singleton
interface RootComponent {
  val daggerWorkerFactory: DaggerWorkerFactory
}

//worker extension point implementation
abstract class ListenableWorker(val workerParameters: String)

@Target(AnnotationTarget.FUNCTION)
@Retention
@MapKey
annotation class WorkerKey(val value: KClass<out ListenableWorker>)

@Scope
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
annotation class WorkerScope

@Module(subcomponents = [WorkerFactoryComponent::class])
abstract class WorkerFactoryModule

@Subcomponent
//@WorkerScope
interface WorkerFactoryComponent {

  fun workerFactories(): Map<Class<out ListenableWorker>, Provider<ListenableWorker>>

  @Subcomponent.Factory
  interface Factory {
    fun create(@BindsInstance workerParameters: String): WorkerFactoryComponent
  }
}

//worker factory
@Singleton
class DaggerWorkerFactory @Inject constructor(
  private val factory: WorkerFactoryComponent.Factory
) {

  fun createWorker(
    workerClassName: String,
    workerParameters: String
  ): ListenableWorker {
    val workers = factory
      .create(workerParameters)
      .workerFactories()

    val workerClass = Class
      .forName(workerClassName)
      .asSubclass(ListenableWorker::class.java)

    val worker = workers.getValue(workerClass).get()

    return worker
  }
}

//feature worker implementation
@Module
abstract class FeatureModule {
  @Binds @IntoMap @WorkerKey(FeatureWorker::class) abstract fun impl(w: FeatureWorker): ListenableWorker
}

//@WorkerScope
class FeatureWorker @Inject constructor(workerParameters: String) : ListenableWorker(workerParameters)

//test
class DaggerWorkerTest {

  @Test
  fun testWorkerInjection() {
    val component = dagger.reflect.DaggerReflect.create(RootComponent::class.java)
//    val component = DaggerRootComponent.create()
    val daggerWorkerFactory = component.daggerWorkerFactory
    val featureWorker = daggerWorkerFactory.createWorker(
      workerClassName = FeatureWorker::class.java.name,
      workerParameters = "foo"
    )

    assertEquals("foo", featureWorker.workerParameters)
  }
}

Running the test gives the following error:

java.lang.IllegalStateException: Missing binding for java.lang.String
 * Requested: foo.FeatureWorker
     from @Inject[foo.FeatureWorker.<init>(…)]
 * Requested: java.lang.String
     which was not found.

If you uncomment 2 lines with //@WorkerScope, you'll get a different(and incorrect) error about invalid scopes:

java.lang.IllegalStateException: Unable to find binding for key=foo.FeatureWorker with linker=Linker with Scope[@javax.inject.Singleton()]

Note that both cases work as expected with dagger codegen.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions