Search code examples
androidkotlindependency-injectiondagger-hiltassisted-inject

@AssistedInject parameters in Hilt modules


I'm having trouble migrating a project from Dagger to Hilt.

The legacy code uses a class Foo that has configuration for the app itself (like country code and such), an instance of this Foo class is retrieved after the user chooses a region from a button that triggers an async call.

This Foo instance is used to create a FooModule and is assigned to the dependency graph as follows:

override fun onFooReceived(foo: Foo) {
   DaggerFooComponent.Builder()
      .fooModule(FooModule(foo))
      .mainAppComponent(appComponent)
      .build()
}

Also, this Foo class is used to create a Factory class that returns instances of different classes (like RetrofitApiClients) depending on properties of said foo class.

The FooModule class looks something like this:

@Module
class FooModule(private val foo: Foo) {
   @Provides
   fun provideFoo(): Foo = foo

   @Provides
   fun provideRetrofitClient(): RetrofitClient = foo.factory.getRetrofitClient(foo)

   @Provides
   fun provideOtherClass(): OtherClass = foo.factory.getOtherClass(foo)
}

The FooFactory class is just an interface with different implementations that instanciate classes dependending on Foo properties.

interface FooFactory {
   fun getOtherClass(foo: Foo): OtherClass
   fun getRetrofitClient(foo: Foo): RetrofitClient
}

implementations looks like follow:

class NorthRegionFooFactory: FooFactory {
   override fun getOtherClass(foo: Foo) = OtherClass(foo.regionId)
   override fun getRetrofitClient(foo: Foo) = RetrofitClient(foo.getConfig("base_url_key"))
}

class SouthRegionFooFactory: FooFactory {
   override fun getOtherClass(foo: Foo) = throw Exception("OtherClass not supported in this region")
   override fun getRetrofitClient(foo: Foo) = RetrofitClient(SOUTH_REGION_MOCK_CONSTANT)
}

What I tried so far

What came to my mind to solve this was @AssistedInject, I modified the code to look like this:

FooModule:

@Module
@InstallsIn(SingletonComponent::class)
class FooModule @AssistedInject constructor(@Assisted val foo: Foo) {
   @Provides
   fun provideFoo(): Foo = foo

   @Provides
   fun provideRetrofitClient(): RetrofitClient = foo.factory.getRetrofitClient(foo)

   @Provides
   fun provideOtherClass(): OtherClass = foo.factory.getOtherClass(foo)
}

FooModuleFactory (new class)

@AssistedFactory
interface FooModuleFactory {
   fun create(foo: Foo): FooModule
}

And when receiving the Foo instance...

SelectRegionActivity

@AndroidEntryPoint
class SelectRegionActivity: AppCompatActivity {
   @Inject
   lateinit var fooModuleFactory: FooModuleFactory

   override fun onFooReceived(foo: Foo) {
      fooModuleFactory.create(foo)
   }
}

but this doesn't work because Hilt needs empty constructor for modules:

public final class FooModule {
             ^
  Modules that need to be instantiated by Hilt must have a visible, empty constructor.
  [Hilt] Processing did not complete. See error above for details.

I tried adding the @Assisted Foo constructor as a secondary constructor for FooModule and havign the primary constructor empty but I get the same error.

Is possible to have @Assisted properties within Hilt modules?


Solution

  • As error states, all modules must have empty constructors, otherwise dagger won't be able to create instance of the module classes referenced in components. Modules will always work as map how to find dependencies.

    You need to add Foo as dependency in the classes you are trying to inject, not the dagger modules. In that case, you will be able to use assisted injection.