I have a setup something like this:
interface Animal {
}
class Dog @Inject constructor() : Animal
class AnimalProxy @Inject constructor(
val animalFactory: AnimalFactory,
val animalMap: Map<AnimalType, Animal>
) : Animal
enum class AnimalType {
Pet,
Wild
}
class AnimalFactory @Inject constructor ()
And this is how I am binding these object in the module
@Module
@InstallIn(SingletonComponent::class)
class AnimalModule {
@MapKey
annotation class AnimalTypeKey(val value: AnimalType)
@Named(DOG)
@Provides
fun provideDog(
): Animal {
return Dog()
}
@Provides
@Singleton
@IntoMap
@AnimalTypeKey(AnimalType.Pet)
@Named(PROXY)
fun provideAnimalProxy(
animalProxy: AnimalProxy
) : Animal = animalProxy
companion object {
const val DOG = "dog"
const val PROXY = "proxy"
}
}
But somehow something is not quite right and I am not able to figure out what's going on. I get an error cannot be provided without an @provides-annotated method
. I know something is wrong when I am creating provideAnimalProxy
but can't figure it out. Other working option I have is:
@InstallIn(SingletonComponent::class)
@Module
class AnimalsModule {
@Singleton
@Provides
fun provideProxy(
): Animal {
return AnimalProxy(
AnimalFactory(),
mapOf(
AnimalType.Pet to Dog(),
),
)
}
}
But this one feels redundant as for AnimalFactory
I already have an inject
constructor.
@Named(DOG)
@Provides
fun provideDog(
): Animal { // Provides @Named(DOG) Animal
return Dog() // which will return a Dog()
}
@Provides
@Singleton
@IntoMap
@AnimalTypeKey(AnimalType.Pet) // Binds the key Pet
@Named(PROXY) // into @Named(PROXY) Map<AnimalType, Animal>
fun provideAnimalProxy(
animalProxy: AnimalProxy
) : Animal = animalProxy // which will return an AnimalProxy()
What it sounds like you want is for AnimalProxy to be bound, outside of the map, using its @Inject
constructor. That would look like this:
@Provides
@IntoMap // @IntoMap here means Map<key, Animal>
// because the function returns Animal
@AnimalTypeKey(AnimalType.Pet) // @AnimalTypeKey(Pet) means the key is Pet
// Nothing is @Named, so the map isn't named
fun provideDog(
): Animal { // Binds Pet into Map<AnimalType, Animal>
return Dog() // which will return a Dog()
}
Or, since Dog has an @Inject
annotated constructor, you can simplify into:
@Provides
@IntoMap
@AnimalTypeKey(AnimalType.Pet)
fun provideDog(dog: Dog): Animal = dog
or with @Binds
, in an abstract class or interface:
@Binds
@IntoMap
@AnimalTypeKey(AnimalType.Pet)
abstract fun bindDog(dog: Dog): Animal
For AnimalProxy, you shouldn't use a @Provides
method for an object you're injecting; if you want @Singleton
on it you can annotate the AnimalProxy class itself. You can delete that part of the Module entirely.
However, it looks like you might want AnimalProxy to show up in the Map<AnimalType, Animal>
itself, which sounds like a circular reference: to create the Map you need to create each Animal, including the proxy, which requires creating the same Map you're currently trying to create. If that's the case, you do have a workaround: rather than injecting a Map<AnimalType, Animal>
, you can automatically inject a Map<AnimalType, Provider<Animal>>
the same way you can inject a Provider<Dog>
rather than just a Dog
. That way you won't express a need for a real AnimalProxy (or its Map) in the constructor, which keeps Dagger from complaining.