2

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

Hector
  • 4,016
  • 21
  • 112
  • 211
  • 1
    Can you share your code where you are doing this `@Inject lateinit var myExperimentable: MyExperimentable` – 0xAliHn Feb 23 '21 at 17:46
  • 1
    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 Feb 24 '21 at 08:52
  • @EpicPandaForce If you are correct, then I have misunderstood this comment in Android DOCS... 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. -- How do I use "subcomponents as usual"? – Hector Feb 24 '21 at 09:11
  • 1
    https://developer.android.com/training/dependency-injection/dagger-multi-module#dagger-dfm – EpicPandaForce Feb 24 '21 at 09:52
  • @EpicPandaForce, your link above is where I quoted the "subcomponents as usual" NOTE, the problem is, I do not understand how to employ "subcomponents as usual"??? – Hector Feb 24 '21 at 10:05
  • 1
    Actually, you would most likely need component dependency inside a dynamic feature module – EpicPandaForce Feb 24 '21 at 10:38
  • @EpicPandaForce, how do I do that? Can I use Hilt "@"Installin in my DFM? – Hector Feb 24 '21 at 10:43
  • 1
    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 Feb 24 '21 at 12:20
  • @EpicPandaForce, do you know of any GITHUB samples that illustrate how to achieve this with EntryPoint? – Hector Feb 24 '21 at 14:35
  • 1
    Hey @Hector, I am also looking for a solution for the same. I have my Base app with all my code and a database repository as Dynamic Module. What i need is to call the repository class inside database module from my base app. Did you find a proper solution? – Ajith M A May 06 '21 at 17:41
  • 1
    @AjithMemana, yes i found a solution based on this sample code https://github.com/googlesamples/android-dynamic-code-loading – Hector May 07 '21 at 14:06

1 Answers1

1

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}")
    }
}
0xAliHn
  • 18,390
  • 23
  • 91
  • 111