How to inject/provide repository from Android Dynamic Feature module into App module

Viewed 730

Im investigating Dynamic Feature modules in my current Android Application.

My application consists of an App Module, a number of static modules and a single Dynamic Feature Module.

Im attempting to inject a repository class declared in the Dynamic Feature Module into a repository class in one of my static modules.

I have a common shared module that contains an interface that defines the API of

My dynamic feature module has a repository class that resembles this:-

class DynamicFeatureRepository @Inject constructor(private val applicationContext: Context) : MyExperimentable {

    override fun accessDynamicModuleRawData(): List<MyExperimentDO> {
        val myExperimentDOs = mutableListOf<MyExperimentDO>()
        applicationContext.resources.openRawResource(R.raw.data).use {
            val reader = BufferedReader(InputStreamReader(it))
            while (reader.ready()) {
                val columns = reader.readLine().split(",")
                myExperimentDOs.add(
                    MyExperimentDO(
                        myExperimentId = columns[0].toLong(),
                        selected = columns[1].toBoolean(),
                        myExperimentName = columns[2],
                        myExperiment = columns[3]
                    )
                )
            }
        }

        return myExperimentDOs
    }

    override fun generate(data: String): Map<String, String> {

        return emptyMap()
    }
}

I have another static module that uses the above Dynamic Feature Module repository

I believe Dagger subcomponents will allow me to inject this Dynamic Feature Module repository into my static modules classes, however I cannot see how to achieve this.

So far I have the following Dagger classes declared in my common module:-

import dagger.Module
import dagger.Subcomponent
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent

@Subcomponent
interface MyExperimentComponent {
    fun inject(myExperimentable: MyExperimentable)

    @Subcomponent.Factory
    interface Factory {
        fun create(): MyExperimentComponent
    }
}

@InstallIn(SingletonComponent::class)
@Module(subcomponents = [MyExperimentComponent::class])
class SubcomponentsModule {}

In my Dynamic Feature Module I have this Dagger class:-

@InstallIn(SingletonComponent::class)
@Module
class ExperimentModule() {
    @Provides
    fun getMyExperiment(@ApplicationContext appContext: Context): MyExperimentable {
       return DynamicFeatureRepository(appContext)
    }
}

In my static module I have this @Inject

@Inject
lateinit var myExperimentable: MyExperimentable

My Application builds and runs, however the @Inject in my static module is not being satisfied as my app fails with

kotlin.UninitializedPropertyAccessException: lateinit property myExperimentable has not been initialized

Where have I gone wrong?

How do I @Provide my dynamic feature module repo for one of my static modules

Below is the "Injection Site" within my static module:-

class MyRepository @Inject constructor(private val datastore: DataStore, private val workMonitor: WorkMonitor) {

    @Inject
    lateinit var myExperimentable: MyExperimentable

...

    fun accessDynamicModuleRawData() {
        val rawData = myExperimentable.accessDynamicModuleRawData()
        Timber.i("xxx ${rawData.size}")
    }
}

In the Android docs theres this Highlighted NOTE:-

Note: This issue happens whenever you want to create a subcomponent of ApplicationComponent. If you need to create a regular gradle module that depends on a feature module and needs to create a component that depends on a component defined in that feature module, you can use subcomponents as usual.

Isnt this the case I have? My static module needs/depends on a component defined within my Dynamic Feature Module. This comment makes me believe I can employ Dagger subcomponents to solve my problem. If this is true "How" do I use subcomponents to enable Injection of my DFM repo into my static module repo?

UPDATE

I have added this @EntryPoint to my App module:-

@EntryPoint
@InstallIn(SingletonComponent::class)
interface MyExperimentableEntryPointInterface {

    fun getMyExperimentable(): MyExperimentable

}

which will allow me to use this in my target Repository

val bar = EntryPoints.get(appContext, MyExperimentableEntryPointInterface::class.java).getMyExperimentable()

The problem I am encountering is how to "Provide" the implementation of MyExperimentable from my DFM

In my DFM I have tried this:-

@Module
@DisableInstallInCheck
object DynamicFeatureRepositoryeModule {

    @Provides
    @Singleton
    fun provideDynamicFeatureRepository(): MyExperimentable {
        return DynamicFeatureRepository()
    }
}

however I get this exception at build time:-

error: [Dagger/MissingBinding] MyExperimentable cannot be provided without an @Provides-annotated method.

What am I missing to be able to provide an instance of my DFM repository into my App?

UPDATED SOLUTION

I managed to achieve the desired result using Dagger and basing my solution on this sample The aspect I do not like though is this employs reflection

1 Answers

Why don't you add the MyExperimentable dependency as a constructor injection in MyRepository class. Usually, we do field injection for activity/view/fragment. If we do it using constructor injection then you will get the exact error cause during compile time.

May something like this:

class MyRepository @Inject constructor(private val datastore: DataStore, private val workMonitor: WorkMonitor, private val myExperimentable: MyExperimentable) {

    //@Inject
    //lateinit var myExperimentable: MyExperimentable

...

    fun accessDynamicModuleRawData() {
        val rawData = myExperimentable.accessDynamicModuleRawData()
        Timber.i("xxx ${rawData.size}")
    }
}
Related