Search code examples
androiddagger-2dagger

dagger2, how can i call @BindsInstance denoted method of SubComponent..?


please help me! I have a trouble in use of dagger 2.

I want to bind some dependency in runtime not in compile time inside MainActivity by using @Subcomponent.Builder and @BindsInstance

I have an ApplicationComponent and it has a Builder and its @BindsInstance looks working fine. I can use like below

DaggerApplicationComponent
    .builder()
    .application(this)
    .build()
    .inject(this)

but some trouble came from MainActivity...

below are snippets of codes

[ApplicationComponent]

@Singleton
@Component(modules = [ApplicationModule::class])
internal interface ApplicationComponent : AndroidInjector<MyApplication> {
    @Component.Builder
    interface Builder {
        @BindsInstance
        fun application(application: Application): Builder
        fun build(): ApplicationComponent
    }
}

[ApplicationModule]

@Module(
        includes = [
            AndroidInjectionModule::class,
            AndroidSupportInjectionModule::class,
            ActivityInjectionModule::class
        ],
        subcomponents = [
            MainComponent::class
        ]
)
internal abstract class ApplicationModule {

    @PerActivity
    @ContributesAndroidInjector(modules = [SplashModule::class])
    abstract fun splashActivity(): SplashActivity

    @Binds
    @IntoMap
    @ActivityKey(MainActivity::class)
    abstract fun mainActivity(builder: MainComponent.Builder): AndroidInjector.Factory<out Activity>

}

[MainComponent]

@PerActivity
@Subcomponent(modules = [MainModule::class])
internal interface MainComponent : AndroidInjector<MainActivity> {
    @Subcomponent.Builder
    abstract class Builder : AndroidInjector.Builder<MainActivity>()  {
        @BindsInstance
        abstract fun testClass(mainTestClass: MainTestClass): Builder
    }
}

[MainActivity]

internal class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
//        This works find without runtime injection
//        AndroidInjection.inject(this)

        /**
         *I want to bind some dependency(in this case, MainTestClass) in runtime like below.
         * so that I can use MainTestClass inside MainModule to inject this to other classes.
         * but, for some reason,
         * DaggerMainComponent IS NOT GENERATED AUTOMATICALLY...
         */
        DaggerMainComponent.builder()
                .testClass(MainTestClass())
                .build()
                .inject(this);

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        startActivity(Intent(this, SplashActivity::class.java))
    }

}

The problem is that I cannot access DaggerMainComponent because the Dagger doesn't generate it automatically. I am searching for lots of websites to solve this but failed. Is there any way to make it?


Solution

  • I have found a way to achieve what you want, I think. Apologies for not using your particular example, but it was easier to paste in the code I have and know that works from my own IDE. I've added comments on the critical lines. Here's the code:

    Singleton component

    @Singleton
    @Component(modules = [
      AndroidSupportInjectionModule::class,
      RuntimeBindingModule::class // my addition!
    ])
    interface MainApplicationComponent {
    
      fun inject(app: MainApplication)
    
      // my addition!
      fun runtimeBuilder(): RuntimeBindingActivitySubcomponent.Builder
    
      @Component.Builder
      interface Builder {
        fun build(): MainApplicationComponent
        @BindsInstance fun app(app: Context): Builder
      }
    }
    

    This binding code is essentially identical to yours.

    @Subcomponent
    interface RuntimeBindingSubcomponent : AndroidInjector<RuntimeBindingActivity> {
      @Subcomponent.Builder
      abstract class Builder : AndroidInjector.Builder<RuntimeBindingActivity>() {
        @BindsInstance abstract fun bindInt(intVal: Int): Builder
      }
    }
    
    @Module(subcomponents = [RuntimeBindingSubcomponent::class])
    abstract class RuntimeBindingActivityModule {
      @Binds @IntoMap @ActivityKey(RuntimeBindingActivity::class)
      abstract fun bindInjectorFactory(
        builder: RuntimeBindingActivitySubcomponent.Builder
      ): AndroidInjector.Factory<out Activity>
    }
    

    MainApplication

    open class MainApplication : Application(), HasActivityInjector {
    
      // This needs to be accessible to your Activities
      lateinit var component: MainApplication.MainApplicationComponent
    
      override fun onCreate() {
        super.onCreate()
        initDagger()
      }
    
      private fun initDagger() {
        component = DaggerMainApplicationComponent.builder()
          .app(this)
          .build()
        component.inject(this)
      }
    }
    

    RuntimeBindingActivity

    class RuntimeBindingActivity : AppCompatActivity() {
    
      // I had to use @set:Inject because this is a primitive and we can't use lateinit 
      // on primitives. But for your case, 
      // `@Inject lateinit var mainTestClass: MainTestClass` would be fine
      @set:Inject var intVal: Int = -1
    
      override fun onCreate(savedInstanceState: Bundle?) {
        // And this is how you can get runtime binding
        val subComponent = (application as MainApplication).component.runtimeBuilder()
        with(subComponent) {
          seedInstance(this@RuntimeBindingActivity)
          bindInt(10) // runtime binding
          build()
        }.inject(this)
    
        Log.d("RuntimeBindingActivity", "intVal = $intVal")
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_runtime_binding)
      }
    }
    

    It is critically important to note that the subcomponent which you generate in this way doesn't get magically stored somewhere by dagger. If you want your late-bound instance to be available for injecting into other classes controlled by your @PerActivity scope, you need to manually manage the lifecycle of this subcomponent. Store it somewhere (maybe in your custom Application class), and then you also must set its reference to null when your activity is destroyed, or you'll be leaking that activity.