google / dagger

A fast dependency injector for Android and Java.
https://dagger.dev
Apache License 2.0
17.36k stars 2k forks source link

Hilt injecting generic interface: Missing Binding #2529

Closed jonapoul closed 3 years ago

jonapoul commented 3 years ago

I feel like I may be running into a limitation of Hilt here, but I'll try my best anyway. Basically, I'm trying to inject an instance of a generic interface where the type parameter is also an interface. A representative example of my scenario (redacted for privacy and simplicity):

interface IApi // just a base interface to identify my various Retrofit APIs

interface IApiBuilder<out T : IApi> {
    fun buildApi(): T
}

interface ISomeWebsiteApi : IApi {
    @GET("/endpoint/value")
    suspend fun getValue(): String
}

Then I declare an implementation of IApiBuilder as so:

class SomeWebsiteApiBuilder @Inject constructor() : IApiBuilder<ISomeWebsiteApi> {
    override fun buildApi(): ISomeWebsiteApi {
        return Retrofit.Builder()
            .someOtherConfig()
            .build()
            .create(ISomeWebsiteApi::class.java)
    }
}

I initially tried using a binding module to inject this class into my ViewModel, as below:

@Module
@InstallIn(SingletonComponent::class)
abstract class BindsSomeWebsiteApiBuilderModule {
    @Binds
    @Singleton
    abstract fun bindsApiBuilder(builder: SomeWebsiteApiBuilder): IApiBuilder<ISomeWebsiteApi>
}

But I get the following build error:

C:\dev\myapp\app\build\generated\source\kapt\standardDebug\com\example\myapp\MyApplication_HiltComponents.java:169: error: [Dagger/MissingBinding] com.example.myapp.IApiBuilder<? extends com.example.myapp.ISomeWebsiteApi> cannot be provided without an @Provides-annotated method.
  public abstract static class SingletonC implements MyApplication_GeneratedInjector,
                         ^
      com.example.myapp.IApiBuilder<? extends com.example.myapp.ISomeWebsiteApi> is injected at
          com.example.myapp.MyViewModel(�, apiBuilder)
      com.example.myapp.MyViewModel is injected at
          com.example.myapp.MyViewModel_HiltModules.BindsModule.binds(vm)
      @dagger.hilt.android.internal.lifecycle.HiltViewModelMap java.util.Map<java.lang.String,javax.inject.Provider<androidx.lifecycle.ViewModel>> is requested at
          dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.ViewModelFactoriesEntryPoint.getHiltViewModelMap() [com.example.myapp.MyApplication_HiltComponents.SingletonC ? com.example.myapp.MyApplication_HiltComponents.ActivityRetainedC ? com.example.myapp.MyApplication_HiltComponents.ViewModelC]

Due to the error message I then tried a providing module instead of binding, as below:

@Module
@InstallIn(SingletonComponent::class)
class ProvidesSomeWebsiteApiBuilderModule {
    @Provides
    @Singleton
    fun providesApiBuilder(): IApiBuilder<ISomeWebsiteApi> = SomeWebsiteApiBuilder()
}

But I ended up with the same error message. Note that in reality, my SomeWebsiteApiBuilder has several other injected dependencies as part of this providing module.

My guess is that my issue is stemming from the fact that Hilt can't find an implementation of ISomeWebsiteApi to inject as a type parameter, but is there any way around this with Hilt? I have several Retrofit API interfaces that need building and it would be extremely convenient to be able to use this kind of architecture.

jonapoul commented 3 years ago

Fixed my own issue 30 mins later! All it took was replacing:

interface IApiBuilder<out T : IApi> {

with

interface IApiBuilder<T : IApi> {

So removing the out. Huzzah!

aenadgrleey commented 1 year ago

omg, you have saved my night!