Search code examples
androidkotlindependency-injectiondagger-2dynamic-feature-module

Single instance for different scopes with dagger 2


Problem

I am building an app with a dynamic feature.

To provide all the dependencies to the main module and the feature module I am using dagger 2. The feature component is depending on the main component and because of that, the feature component is having a different scope than the main component scope (@Singleton in that case)

One of the interface injected in the main module are implemented on the feature module and provided by reflection in the main module. The implementation is also used in the feature module.

The problem that I have is that the instance provided in the main module is different from the one in the feature module (because of the scopes) but I would like to have just one instance provided with dagger.

Code

Here some code and you can find the whole example project in github

Dagger configuration for the main module:

TestModule.kt

@Module
class TestModule {

    @Provides
    @Singleton
    fun provideTestA() : TestA = TestAImplementation()

    private var testCProvider: TestC?= null

    @Provides
    @Singleton
    fun provideTestC(testComponent: TestComponent) : TestC {
        if(testCProvider != null) return testCProvider as TestC

        val provider = Class.forName("com.example.feature.services.TestCImplementation\$Provider").kotlin.objectInstance as TestC.Provider
        return provider
            .get(testComponent)
            .also { testCProvider = it }
    }
}

TestComponent.kt

@Singleton
@Component(modules = [TestModule::class])
interface TestComponent {
    fun inject(activity: MainActivity)

    fun provideTestA() : TestA
}

Dagger configuration for the feature module:

TestDependencyModule.kt

@Module
class TestDependencyModule {

    @Provides
    @TestScope
    fun provideTestB(): TestB = TestBImplementation()

    @Provides
    @TestScope
    fun provideTestC(testB: TestB): TestC = TestCImplementation(testB)
}

TestDependencyComponent.kt

@TestScope
@Component(
    modules = [TestDependencyModule::class],
    dependencies = [TestComponent::class]
)
interface TestDependencyComponent {
    fun inject(receiver: TestBroadcastReceiver)

    fun testC(): TestC
}

Expected result

The interfaces TestC and TestA are injected in the MainActivity

The interfaces TestB and TestA are injected in the TestBroadcastReceiver

As expected the instance of the TestA implementation is unique but for the implementation of the TestB is not that way. As TestC depends on TestB the one injected in TestC is different from the one injected in the TestBroadcastReceiver with the @TestScope annotation.

So running the example project that you can find here I get the following log output

Instances injected in the MainActivity

D/TestB: instance 40525431
D/TestC: instance 119319268
D/TestA: instance 60713805

Instances injected in the TestBroadcastReceiver

D/TestB: instance 219966227
D/TestA: instance 60713805

I would like to share the same instance of TestB in both modules.

Any suggestion? Thanks in advance!


Solution

  • I was building two instances of the DaggerTestDependencyComponent one in the Injector and another different one when you are implementing TestC

    The solution that I found was the following:

    • Create an object where I can instantiate the TestDependencyComponent that will be shared with the Injector and the TestCImplementation

      object FeatureInjector {
      
          val testDependencyComponent: TestDependencyComponent by lazy {
              DaggerTestDependencyComponent.builder()
                  .testComponent(com.example.daggertest.dagger.Injector.testComponent)
                  .build()
          }
      }
      
    • Now I modified my feature Injector like that:

      object Injector {
      
          lateinit var testDependencyComponent: TestDependencyComponent
      
          @JvmStatic
          internal fun getTestDependencyComponent(): TestDependencyComponent {
              if (!::testDependencyComponent.isInitialized) {
                  testDependencyComponent = FeatureInjector.testDependencyComponent
              }
              return testDependencyComponent
          }
      }
      
    • And the TestCImplementation as follow:

      class TestCImplementation @Inject constructor(
          private val testB: TestB
      ) : TestC {
          override fun testCFun() {
              testB.testBFun()
              Log.d("TestC", "instance ${System.identityHashCode(this)}")
          }
      
          companion object Provider : TestC.Provider {
              override fun get(testComponent: TestComponent): TestC {
                  return FeatureInjector.testDependencyComponent.testC()
              }
          }
      }
      

    Running the code now I am getting the same instance of the TestB