JakeWharton / dagger-reflect

A reflection-based implementation of the Dagger dependency injection library for fast IDE builds.
Apache License 2.0
655 stars 44 forks source link

Subcomponent + BindsInstance doesn't work #212

Open Dkhusainov opened 3 years ago

Dkhusainov commented 3 years ago

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.

Dkhusainov commented 3 years ago

After further investigation it looks like a duplicate of #208