Passing an interceptor to retrofit builder using dagger-hilt - android

I'm learning about retrofit interceptors, for work purposes I'm using dagger-hilt for the injection of dependencies to fragments etc. I wrote a custom interceptor to check for connection errors and I'm trying to add it to the Retrofit.Builder():
#Provides
#Singleton
fun provideApi(): StoreApi {
return Retrofit.Builder()
.baseUrl(Constants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
.build()
.create(StoreApi::class.java)
}
however, I have no clue how to pass that:
val okHttpClient = OkHttpClient()
.newBuilder()
.addInterceptor(ConnectivityInterceptor)
.build()
as a .client() to the retofit builder (even with dagger-hilt), any ideas?

You can setup a module something like this where you provide all the dependency requirements as functions and exposing them to dagger through #Provides then leave dagger to provide the dependencies as function arguments to build the dependency graph :
#Module class ApiModule {
#Provides
#Singleton
internal fun provideApi(retrofit: Retrofit): StoreApi {
return retrofit
.create(StoreApi::class.java)
}
#Provides
#Singleton
internal fun retrofit(client: OkHttpClient): Retrofit =
Retrofit.Builder()
.client(client)
.baseUrl(Constants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
.build()
#Provides
#Singleton
internal fun client(connectivityInterceptor: ConnectivityInterceptor): OkHttpClient =
OkHttpClient.Builder()
.addInterceptor(connectivityInterceptor)
.build()
#Provides
#Singleton
internal fun interceptor(): ConnectivityInterceptor = ConnectivityInterceptor()
}
This is a trivial example based on the supplied code.

Related

#InstallIn-annotated classes must also be annotated with #Module or #EntryPoint error

I don't know why I am receiving " #InstallIn-annotated classes must also be annotated with #Module or #EntryPoint: com.example.newsapp.di.AppModule
[Hilt] Processing did not complete. See error above for details." error. I used the tutorial to write this code and the tutor's code (exactly the same as mine) works correctly.
`
#Module
#InstallIn(SingletonComponent::class)
object AppModule {
#Provides
fun baseURL() = BASE_URL
#Provides
fun logging() = HttpLoggingInterceptor()
.setLevel(HttpLoggingInterceptor.Level.BODY)
#Provides
fun okHttpClient() = okhttp3.OkHttpClient.Builder()
.addInterceptor(logging())
.build()
#Provides
#Singleton
fun provideRetrofit(baseUrl: String): NewsService =
Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient())
.build()
.create(NewsService::class.java)}`
I checked imports and found that I imported Module from the wrong place.

How to generate objects with the same type in Hilt?

#Module
#InstallIn(SingletonComponent::class)
object NetworkModule {
#Singleton
#Provides
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("http://example.com/")
.client(okHttpClient)
.build()
}
#Singleton
#Provides
fun provideMyRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("http:/my.com/")
.client(okHttpClient)
.build()
}
}
Their difference is only baseUrl.
I tried to solve this problem by use #Qualifier.
interface RetrofitQualifier {
#Qualifier
#Retention(AnnotationRetention.BINARY)
annotation class Retrofit
#Qualifier
#Retention(AnnotationRetention.BINARY)
annotation class MyRetrofit
}
#Module
#InstallIn(SingletonComponent::class)
object NetworkModule {
#Singleton
#Provides
#RetrofitQualifier.Retrofit
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("http://example.com/")
.client(okHttpClient)
.build()
}
#Singleton
#Provides
#RetrofitQualifier.MyRetrofit
fun provideMyRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("http:/my.com/")
.client(okHttpClient)
.build()
}
}
And I use it by use #RetrofitQualifier.MyRetrofit in my class:
class MyRepository #Inject constructor(
application: Application
) {
...
#Inject
#RetrofitQualifier.MyRetrofit
lateinit var retrofit:Retrofit
private val service: Service = retrofit.create(Service::class.java)
...
}
However, I was failed, the log is
kotlin.UninitializedPropertyAccessException: lateinit property retrofit has not been initialized
What should I do? Maybe use #Named? I am not sure...
Example with Qualifier, you can add this in the same file where you have your providers or even create a RetrofitQualifier.kt file and add them there.
#Qualifier
#Retention(AnnotationRetention.BINARY)
annotation class RetrofitOne
#Qualifier
#Retention(AnnotationRetention.BINARY)
annotation class RetrofitTwo
And the #Provides
#Singleton
#Provides
#RetrofitOne
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("http://example.com/")
.client(okHttpClient)
.build()
}
#Singleton
#Provides
#RetrofitTwo
fun provideMyRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("http:/my.com/")
.client(okHttpClient)
.build()
}
Then in your Repository you can inject using two options
Field injection
// At field injection.
#AndroidEntryPoint
class MyRepository #Inject constructor(...) {
#RetrofitOne
#Inject lateinit var retrofit: Retrofit
}
As a dependency injected-constructor class
// As a dependency of a constructor-injected class.
class MyRepository #Inject constructor(
#RetrofitTwo private val retrofit: Retrofit
) : ...
But the thing is that perhaps you installedIn in another module where your Repository doesn't have visibility.
About the #Named you can still use it but as per the documentation is recommended to use Qualifier

How to remove Dagger Hilt dependency Injection cycle

I have a network module class that provides an ApiService instance.
There is an Authenticator class which refreshes access token when expired.
The authenticator requires ApiService instance for making API calls.
This causes a cyclic dependency. How to avoid this?
Now I'm creating a new ApiService inside TokenExpiryAuthenticator class to make API calls, to break the cyclic dependency.
How to properly inject ApiService into TokenExpiryAuthenticator without causing cyclic dependency?
#InstallIn(SingletonComponent::class)
#Module
object NetworkModule {
#Provides
#Singleton
#Named("Other")
fun provideRetrofitWithoutInterceptor(#Named("Other") client: OkHttpClient, gson: Gson): Retrofit {
return Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
}
#Provides
#Singleton
fun provideRetrofit(client: OkHttpClient, gson: Gson): Retrofit {
return Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
}
#Provides
#Singleton
fun providesOkHttpClient(httpLoggingInterceptor: HttpLoggingInterceptor, supportInterceptor: SupportInterceptor, tokenExpiryAuthenticator: TokenExpiryAuthenticator): OkHttpClient {
return OkHttpClient.Builder().writeTimeout(1, TimeUnit.MINUTES)
.readTimeout(1, TimeUnit.MINUTES)
.callTimeout(1, TimeUnit.MINUTES)
.addInterceptor(httpLoggingInterceptor)
.addInterceptor(supportInterceptor)
.authenticator(tokenExpiryAuthenticator)
.build()
}
#Named("Other")
#Provides
#Singleton
fun providesOkHttpClientWithoutInterceptor(httpLoggingInterceptor: HttpLoggingInterceptor): OkHttpClient {
return OkHttpClient.Builder().writeTimeout(1, TimeUnit.MINUTES)
.readTimeout(1, TimeUnit.MINUTES)
.callTimeout(1, TimeUnit.MINUTES)
.addInterceptor(httpLoggingInterceptor)
.build()
}
#Provides
#Singleton
fun providesHttpLoggingInterceptor(): HttpLoggingInterceptor {
return if (BuildConfig.DEBUG)
HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
else
HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.NONE
}
}
#Provides
#Singleton
fun providesGson(): Gson {
return GsonBuilder().create()
}
#Provides
#Singleton
fun providesRestApiService(retrofit: Retrofit): ApiService {
return retrofit.create(ApiService::class.java)
}
#Provides
#Singleton
#Named("Other")
fun providesRestApiServiceWithoutInterceptor(#Named("Other") retrofit: Retrofit): ApiService{
return retrofit.create(ApiService::class.java)
}
}
You could split your ApiService and create a new AuthenticationApi which only includes the endpoints to authenticate or refresh your access tokens. This AuthenticationApi is created with an OkHttp instance without your Authenticator.
That way your Authenticator only needs reference to this slim Retrofit api.
This also guarantees that you don't get HTTP 401 authentication loops in case of wrong credentials when using your authentication endpoints.
Simplest possible solution is to inject the dependency lazily.
class TokenExpiryAuthenticator #Inject constructor(
private val api: Lazy<ApiService>,
private val persistence: TokenPersistenceRepository
) : Authenticator {
override fun authenticate(route: Route?, response: Response): Request? {
// If HTTP 401 error was raised while trying to refresh token it means
// the token is invalid (or expired). Sign out user and do not
// proceed with further requests.
if (response.request.url.encodedPath == REFRESH_TOKEN_PATH) {
// Perform any cleanup needed.
// Return null so no further requests will be performed.
return null
}
// Request failed for old token. It's about time to refresh it.
if (response.request.header(AUTH_HEADER) != null) {
// Refresh access token using your lazily injected service.
persistence
.retrieveRefreshToken()
.flatMap(api.get()::refreshToken)
.flatMapCompletable {
Completable.concatArray(
persistence.saveAccessToken(it.accessToken),
persistence.saveRefreshToken(it.refreshToken),
)
}
.blockingAwait()
// You can use blocking await as the [authenticate] method is
// run on I/O thread anyway.
}
// Load recently refreshed access token.
val token = (...)
// Format authorization header value.
val header = (...)
// Modify and proceed with a request with refreshed access token.
return response.request.newBuilder()
.removeHeader(AUTH_HEADER)
.addHeader(AUTH_HEADER, header)
.build()
}
}

Hilt - Should I #Provides my Base URL or API Key?

I am new to Hilt and have negligible experience in Dagger 2 as well.
I am now doing a simple sample project for my self-learning, and trying to apply Hilt.
I use Retrofit to call APIs.
So, I am trying to refactor my code into using Hilt:
#Module
#InstallIn(SingletonComponent::class)
object WeatherAPIModule {
private const val BASE_URL = "https://api.openweathermap.org/data/2.5/"
#Provides
fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor {
return HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
}
#Provides
fun provideOkHttpClient(loggingInterceptor: HttpLoggingInterceptor): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.build()
}
#Provides
fun provideGsonConverterFactory(): GsonConverterFactory {
return GsonConverterFactory.create()
}
#Provides
fun provideRetrofit(okHttpClient: OkHttpClient, gsonConverterFactory: GsonConverterFactory): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(gsonConverterFactory)
.build()
}
#Provides
fun provideWeatherService(retrofit: Retrofit): OpenWeatherService {
return retrofit.create(OpenWeatherService::class.java)
}
val weatherApiKey by lazy {
ApiKeyStore.getWeatherApiKey()
}
}
My question is, Should I use #Provides on Base URL and API Key as well?
Why do I ask
According to my understanding, if I have a class like this:
class SomeClass #Inject constructor() {
#Inject lateinit var someString: String
...
}
Hilt will try to find modules that provides String, and inject that String here. But clearly that's not what I want.
What should be the best way to do this?
Especially for weatherApiKey. I think I should not have leave it as an ordinary function, because that would lose the point of using Hilt at all.

Dependency injection with Koin

I have a class that uses Dagger 2 for dependency injection. Now I want to switch to Koin for dependency injection. There are modules in Koin and I want to make a module out of the class or whatever can be done.
#Module
class NetModule(private val baseUrl: String) {
#Provides
#Singleton
fun providesOkHttpClient(
httpLoggingInterceptor: HttpLoggingInterceptor): OkHttpClient = OkHttpClient.Builder().addInterceptor(
httpLoggingInterceptor).build()
#Provides
#Singleton
fun provideLoggingInterceptor(): HttpLoggingInterceptor {
val interceptor = HttpLoggingInterceptor(
HttpLoggingInterceptor.Logger { message -> Logger.d("NETWORK: $message") })
interceptor.level = HttpLoggingInterceptor.Level.NONE
return interceptor
}
#Provides
#Singleton
fun providesMoshi(): Moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
#Provides
#Singleton
fun providesRetrofit(okHttpClient: OkHttpClient, moshi: Moshi): Retrofit {
return Builder().client(okHttpClient).baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
}
#Provides
#Singleton
fun providesApiInterface(retrofit: Retrofit): ApiInterface = retrofit.create(
ApiInterface::class.java)
}
Koin uses a DSL for describing modules. Usually you'd declare the module itself on a top-level. Since you need to provide baseUrl, you'd have to create a factory for it.
The #Provides annotation is completely irrelevant, but #Singleton needs to be translated and does so with single. To retrieve the dependencies, just call get().
fun netModule(baseUrl: String) = module {
single {
HttpLoggingInterceptor(
HttpLoggingInterceptor.Logger { message ->
Logger.d("NETWORK: $message")
}).apply {
level = HttpLoggingInterceptor.Level.NONE
}
}
single {
OkHttpClient.Builder()
.addInterceptor(get<HttpLoggingInterceptor>())
.build()
}
single {
Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
}
single {
Retrofit.Builder()
.client(get())
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
}
single { get<Retrofit>().create(ApiInterface::class.java) }
}

Categories

Resources