Search code examples
androidkotlindagger-hilt

How to get out of this token authenticator Hilt dependency cycle?


I have a function that returns an API, which needs an OkHttpClient instance

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

    @Provides
    fun provideAuthRetrofitService (@AuthQualifier okHttpClient: OkHttpClient): IQuipsAuth{
        return Retrofit.Builder()
                .baseUrl("${Config.BASE_URL}/")
                .client(okHttpClient)
                .addConverterFactory(GsonConverterFactory.create(GsonBuilder().setLenient().create()))
                .build().create(IQuipsAuth::class.java)
    }

And I also have a function that returns an OkHttpClient that needs an API instance for the authenticator, what do I do?

    @Provides
    @AuthQualifier
    fun provideOkHttpClient (application: Application,  iQuipsAuth: IQuipsAuth): OkHttpClient {
        val interceptor = HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger {
            override fun log(message: String) {
                Log.d("HttpLoggingInterceptor", message)
            }
        }).setLevel(HttpLoggingInterceptor.Level.HEADERS)

        return OkHttpClient.Builder()
                .readTimeout(180, TimeUnit.SECONDS)
                .writeTimeout(45, TimeUnit.SECONDS)
                .hostnameVerifier { _: String?, _: SSLSession? -> true }
                .addInterceptor(interceptor)
                .addInterceptor(AccessTokenInterceptor(application, iQuipsAuth))
                .authenticator(AccessTokenAuthenticator(application, iQuipsAuth))
                .build()

    }
}

I am trying to recreate this same Koin code in Hilt:

single {
        val interceptor = HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger {
            override fun log(message: String) {
                Log.d("HttpLoggingInterceptor", message)
            }
        }).setLevel(HttpLoggingInterceptor.Level.BODY)

        val okHttpClient = OkHttpClient.Builder()
            .readTimeout(180, TimeUnit.SECONDS)
            .writeTimeout(45, TimeUnit.SECONDS)
            .hostnameVerifier { _: String?, _: SSLSession? -> true }
            .addInterceptor(interceptor)
            .build()


        val retrofit = Retrofit.Builder()
            .baseUrl("${Config.baseUrl}/")
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create(get()))
            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
            .build()

        retrofit.create(IQuipsAuth::class.java)
    }

Solution

  • You can workaround dependency cycles using dagger.Lazy<T>.

    fun provideOkHttpClient (application: Application, iQuipsAuth: Lazy<IQuipsAuth>): OkHttpClient {
        // ...
    
                .addInterceptor(AccessTokenInterceptor(application, iQuipsAuth)) // pass lazy
                .authenticator(AccessTokenAuthenticator(application, iQuipsAuth)) // pass lazy
    
        // ...
    }
    

    Then invoke lazy.get() when the client is actually used.

    Please note that this would be more self-evident if the AccessTokenInterceptor and AccessTokenAuthenticator were both provided to Dagger using @Inject constructor, and provided to this @Provides method as arguments of the method.