1
votes

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
Can you share your code where you are doing this @Inject lateinit var myExperimentable: MyExperimentable - 0xAliHn
I'm pretty sure you won't be injecting stuff that may or may not exist at runtime into other things. You can only manually inherit into the dynamic feature module, not the other way around. - EpicPandaForce
Actually, you would most likely need component dependency inside a dynamic feature module - EpicPandaForce
No, Hilt does not support DFM. Hilt only sees what's in the app out of the box. But you can use the EntryPoints accessor to get the SingletonComponent, and inherit from that. - EpicPandaForce

1 Answers

1
votes

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}")
    }
}