Dependency injection with Koin - android

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) }
}

Related

How does Retrofit.create() extenstion function works with no class refrence parameter?

I have a Hilt module to provide Retrofit API in my app like so:
#Module
#InstallIn(SingletonComponent::class)
object NetworkDataModule {
#Provides
#Singleton
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(
HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
)
.build()
}
#Provides
#Singleton
fun provideMyApi(client: OkHttpClient): MyApi {
return Retrofit.Builder()
.baseUrl(MyApi.BASE_URL)
.addConverterFactory(MoshiConverterFactory.create())
.client(client)
.build()
.create()
}
}
And it is my API interface:
interface MyApi {
#GET("search")
suspend fun search(): searchDto
companion object {
const val BASE_URL = "https://myapi.com/"
}
}
The question is how the Retrofits create() extension function knows which class should it return despite I did not pass any information about the MyApi interface to it. The create function implementation is this:
inline fun <reified T> Retrofit.create(): T = create(T::class.java)
How does create(T::class.java) return MyApi in spite of I did not provide any type for T?
My retrofit version is 2.9.0

Setup OkHttp Proxy at runtime when using Hilt

Hilt network module is setup like below:
#Module
#InstallIn(SingletonComponent::class)
class NetworkModule {
private val TIMEOUT_UNIT = TimeUnit.MILLISECONDS
private val TIMEOUT_MILLIS: Long = 10000
#Provides
fun provideApi(retrofit: Retrofit): ApiService =
retrofit.create(ApiService::class.java)
#Singleton
#Provides
fun provideRetrofit(okHttpClient: OkHttpClient, converter: Converter.Factory): Retrofit =
Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.client(okHttpClient)
.addConverterFactory(converter)
.build()
#Provides
fun provideOkHttpClient(
loggingInterceptor: HttpLoggingInterceptor,
): OkHttpClient {
val builder = OkHttpClient.Builder()
builder.retryOnConnectionFailure(true)
builder.connectTimeout(3 * TIMEOUT_MILLIS, TIMEOUT_UNIT)
builder.readTimeout(6 * TIMEOUT_MILLIS, TIMEOUT_UNIT)
builder.writeTimeout(12 * TIMEOUT_MILLIS, TIMEOUT_UNIT)
builder.addInterceptor(loggingInterceptor)
if (condition == true) {
builder.proxy(Proxy(Proxy.Type.HTTP, InetSocketAddress.createUnresolved("localhost", HTTP_PROXY_PORT)))
}
return builder.build()
}
#Provides
fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor {
val loggingInterceptor = HttpLoggingInterceptor()
if (BuildConfig::DEBUG.get()) {
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
} else {
loggingInterceptor.level = HttpLoggingInterceptor.Level.NONE
}
return loggingInterceptor
}
#Provides
fun provideConverterFactory(): Converter.Factory {
return GsonConverterFactory.create()
}
#Provides
fun providesGson(): Gson {
return Gson()
}
}
I want to customize the retrofit instance at runtime with or without the proxy according to some condition. How do I do that?
Koin had unloadModule option for reinitializing modules, does Hilt has any similar feature or behaviour?

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.

Can not change retrofit base url more than once with reflection

I am using reflection for change retrofit base url dynamically but I can change once in activity, after that I can not change it. Why it is not working when I try to change multiple times.
Set dynamic base url using Retrofit 2.0 and Dagger 2
I do not know the domain I just know the end points, I get the domain adress from user.
Just use different modules and api for different URLs. And then use #Named annotation to determine the correct api.
Api module with HOST url
#Module
class ApiModule {
#Provides
#Singleton
#Named("Interceptor")
fun provideInterceptor(context: Context, sharedPrefsStorage: SharedPrefsStorage, #Named("ApiAuth") apiAuth: ApiAuth,realmProvider: DbProvider<Realm>): Interceptor {
return ApiInterceptor(context, sharedPrefsStorage, apiAuth,realmProvider)
}
#Provides
#Singleton
#Named("HttpClient")
fun provideOkHttpClient(#Named("Interceptor") interceptor: Interceptor): OkHttpClient {
val builder = OkHttpClient.Builder()
builder.addInterceptor(interceptor)
builder.readTimeout(30, TimeUnit.SECONDS)
builder.connectTimeout(30, TimeUnit.SECONDS)
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
builder.addInterceptor(loggingInterceptor)
return builder.build()
}
#Provides
#Singleton
#Named("Retrofit")
fun provideRetrofitBuilder(#Named("HttpClient") okHttpClient: OkHttpClient): Retrofit.Builder {
return Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(
GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create()))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.baseUrl(BuildConfig.HOST)
}
#Provides
#Singleton
#Named("Api")
fun provideApi(#Named("Retrofit") builder: Retrofit.Builder): Api {
return builder.build().create<Api>(Api::class.java)
}
companion object {
val TAG = ApiModule::class.java.simpleName
}
}
ApiAuthModule with HOST_2 url
#Module
class ApiAuthModule {
#Provides
#Singleton
#Named("InterceptorAuth")
fun provideInterceptor(): Interceptor {
return ApiAuthInterceptor()
}
#Provides
#Singleton
#Named("HttpClientAuth")
fun provideOkHttpClient(#Named("InterceptorAuth") interceptor: Interceptor): OkHttpClient {
val builder = OkHttpClient.Builder()
builder.addInterceptor(interceptor)
builder.readTimeout(5, TimeUnit.SECONDS)
builder.connectTimeout(5, TimeUnit.SECONDS)
if (BuildConfig.DEBUG) {
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
builder.addInterceptor(loggingInterceptor)
}
return builder.build()
}
#Provides
#Singleton
#Named("RetrofitAuth")
fun provideRetrofitBuilder(#Named("HttpClientAuth") okHttpClient: OkHttpClient): Retrofit.Builder {
return Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(
GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create()))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.baseUrl(BuildConfig.HOST_2)
}
#Provides
#Singleton
#Named("ApiAuth")
fun provideApi(#Named("RetrofitAuth") builder: Retrofit.Builder): ApiAuth {
return builder.build().create<ApiAuth>(ApiAuth::class.java)
}
companion object {
val TAG = ApiModule::class.java.simpleName
}
}
Usage in AppModule:
#Provides
#Singleton
fun worksRepository(#Named("Api") api: Api, #Named("ApiAuth") api2: ApiAuth): IWorksRepository {
return WorksRepository(api, api2)
}

Dagger2 generating multiple instances of retrofit interceptor

Dagger 2 is generating multiple instances of retrofit interceptor despite marking it as singleton in dagger module. Now the problem is that AuthorizationInterceptor constructor gets called twice which I don't understand why and because of that the headers that I set after getting result from login API get sets to a different instance of Interceptor and while making call to some other API which requires authorizationToken the token is unset.
Here is my ApiModule
#Module
open class ApiModule {
#Provides
#Singleton
fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor {
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
return loggingInterceptor
}
#Provides
#Singleton
fun provideHeaderInterceptor(): Interceptor {
return AuthorizationInterceptor()
}
#Provides
#Singleton
fun provideHttpClient(interceptor: HttpLoggingInterceptor, headerInterceptor: Interceptor): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(interceptor)
.addInterceptor(headerInterceptor)
.build()
}
#Provides
#Singleton
fun provideMoshi(): Moshi {
return Moshi.Builder()
.build()
}
#Provides
#Singleton
fun provideRetrofit(client: OkHttpClient, moshi: Moshi, apiConfig: ApiConfig): Retrofit {
return Retrofit.Builder()
.baseUrl(apiConfig.baseUrl)
.client(client)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
}
#Provides
#Singleton
fun provideFrappApi(retrofit: Retrofit): FrappApi {
return retrofit.create(FrappApi::class.java)
}
Here is my AuthorizationInterceptor class
#Singleton
class AuthorizationInterceptor #Inject constructor() : Interceptor {
override fun intercept(chain: Interceptor.Chain?): Response {
val request = chain?.request()
val requestBuilder = request?.newBuilder()
if (request?.header("No-Authorization") == null && authorization.isNotEmpty()) {
requestBuilder?.addHeader("Authorization", authorization)
}
return chain?.proceed(requestBuilder!!.build())!!
}
private var authorization: String = ""
fun setSessionToken(sessionToken: String) {
this.authorization = sessionToken
}
}
You dont need to make a provide method if you do a constructor injection.
Remove the provideHeaderInterceptor method, then update the provideHttpClient method like below,
#Provides
#Singleton
fun provideHttpClient(interceptor: HttpLoggingInterceptor,
headerInterceptor: AuthorizationInterceptor): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(interceptor)
.addInterceptor(headerInterceptor)
.build()
}
Or if you dont like the solution above, you can remove the #Singleton and #Inject in your AuthorizationInterceptor class.

Categories

Resources