Search code examples
androiddependency-injectionautomated-testsdagger-hilt

Hilt error by providing base url for Mockwebserver


In my application I use Hilt for dependency injection. I implemented a RetrofitModule to provide dependencies for it in my Repository like this:

@Module
@InstallIn(ApplicationComponent::class)
object RetrofitModule {

    @Singleton
    @Provides
    fun providesRetrofitClient(okHttpClient: OkHttpClient, baseUrl: String): Retrofit {
        return Retrofit.Builder()
            .baseUrl(baseUrl)
            .addConverterFactory(GsonConverterFactory.create())
            .client(okHttpClient)
            .build()
    }

    @Provides
    fun providesBaseUrl(application: MyApplication): String {
        return application.getBaseUrl()
    }

    @Singleton
    @Provides
    fun providesOkHttpClient(): OkHttpClient {
        val okHttpClientBuilder = OkHttpClient.Builder()
        val loggingInterceptor = HttpLoggingInterceptor().apply {
            level = HttpLoggingInterceptor.Level.BODY
        }
        okHttpClientBuilder.addInterceptor(loggingInterceptor)
        return okHttpClientBuilder.build()
    }

    @Singleton
    @Provides
    fun providesService(retrofit: Retrofit): MyService {
        return retrofit.create(MyService::class.java)
    }
}

To provide a test base url for Mockwebserver configuration under test I implemented the functions inside the MyApplication and MyApplicationTest class

MyApplication.class

class MyApplication : Application() {
    fun getBaseUrl() = "https://www.foo-production.com"
}

MyApplicationTest.class

class MyApplicationTest : Application() {
    fun getBaseUrl() = "http://127.0.0.1:8080"
}

But when I build the app I'm getting this error:

 A binding with matching key exists in component: de.xxx.xxx.MyApplication_HiltComponents.ApplicationC
      ...

I think the problem is this method

@Provides
fun providesBaseUrl(application: MyApplication): String {
    return application.getBaseUrl()
}

and there is a problem by providing MyApplication class


Solution

  • A) Probable fix for your case

    Hilt is not able to provide your exact MyApplication instance, it's only able to provide a generic Application instance (see Component default bindings from Hilt Components).

    This should fix the issue:

    @Provides
    fun providesBaseUrl(application: Application): String {
        return (application as MyApplication).getBaseUrl()
    }
    

    That being said, there's a much more elegant way to implement that.

    B) The elegant way

    1. In src/main/your/package/ create one UrlModule.kt
    @Module
    @InstallIn(ApplicationComponent::class)
    object UrlModule {
    
        @Provides
        fun providesBaseUrl(application: MyApplication): String {
            return "https://www.foo-production.com"
        }
    
    }
    
    1. In src/test/your/package/ (or src/androidTest/your/package/ if it's for UI tests) create the other UrlModule.kt
    @Module
    @InstallIn(ApplicationComponent::class)
    object UrlModule {
    
        @Provides
        fun providesBaseUrl(application: MyApplication): String {
            return "http://127.0.0.1:8080"
        }
    
    }