I am trying to have DI for creating a retrofit instance with KOIN
this is the module
val networkModule = module {
factory { provideRetrofit(get()) }
single { provideNetworkApi(get()) }
}
fun provideRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl(NetworkConstant.BASE_URL)
.addConverterFactory(MoshiConverterFactory.create())
.client(OkHttpClient.Builder().build())
.build()
}
fun provideNetworkApi(retrofit: Retrofit): NetworkCall =
retrofit.create(NetworkCall::class.java)
In the application class
class BaseApp :Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidLogger()
androidContext(this#BaseApp)
modules(listOf(prefModule,networkModule))
}
}
}
The compiler is showing error on provideRetrofit(get()
Too many arguments for public fun provideRetrofit():
I was able to create DI for another class
val prefModule = module {
single { AppPreference(androidContext()) }
}
Can someone point me whats wrong here?
Was being caused because of get()
val networkModule = module {
factory { provideRetrofit() }
single { provideNetworkApi(get()) }
}
Would it be acceptable skip separation of Retrofit instance (what's benefit?) and simply combine it in the goal of creating your NetworkCall?
Here is an example of what I mean:
val appModule = module {
single {
val httpInterceptor = HttpLoggingInterceptor()
httpInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
Retrofit.Builder()
.client(
OkHttpClient.Builder()
.addInterceptor(httpInterceptor).build()
)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("https://postman-echo.com/")
.build()
.create(PostmanEcho::class.java)
}
single {
PostmanEchoRepository(get(), get())
}
....
My PostmanEchoRepository is using equivalent of your NetworkCall as first param.
Details: https://github.com/oradkovsky/weird-data-input/blob/master/app/src/main/java/com/ror/weirddatainput/di/AppModule.kt
Related
I'm new to the Koin so hopefully someone will be able to point out the direction of the issue I'm encountering.
I've an Interface class:
interface UserApi {
#POST("/refreshToken")
#Headers("Accept: application/json")
suspend fun refreshToken(#Body x: X): TokenResponseDto
}
I've a class where I use UserApi to do API call.
class TokenAuthenticator(
private val userApi: UserApi
) : Authenticator {
override fun authenticate(route: Route?, response: Response): Request? = synchronized(this) {
runBlocking { userApi.refreshToken() }
}
}
This far everything is fine, but now I want to Inject TokenAuthenticator class. If I remove constructor for testing purposes, I can see app running and everything is fine, but when I add userApi constructor variable - as I need it, I get and error.
I've NetworkModule that looks like this:
val networkModule = module {
single<UserApi> {
Retrofit.Builder()
.client(get(named("httpClient")))
.baseUrl(get<String>(named("...")))
.addConverterFactory(
...
)
.build()
.create(UserApi::class.java)
}
single(named("httpClient")) {
val tokenAuthenticator: TokenAuthenticator = get()
OkHttpClient.Builder()
.authenticator(tokenAuthenticator)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
}
single {
TokenAuthenticator(get())
}
}
Error:
at org.koin.core.instance.SingleInstanceFactory$get$1.invoke(SingleInstanceFactory.kt:53)
UPDATE: Someone advised to use a lambda function in TokenAuthenticator. I think this solution is more simplier.
class TokenAuthenticator(
private val userApi : () -> UserApi
) {
// ...
fun authenticate(...) {
userApi().refreshToken()
}
}
In this case you can define your koin definition like this.
single {
TokenAuthenticator {
get()
}
}
My answer was:
There may be better solutions but this is a rushed one. You may improve it.
Let's decouple TokenAuthenticator and UserApi. They will be connected later by a TokenRefresher.
interface TokenRefresher {
fun refreshToken()
}
class TokenAuthenticator(
private val tokenRefresher: TokenRefresher
) : Authenticator {
override fun authenticate(route: Route?, response: Response): Request? = synchronized(this) {
runBlocking { tokenRefresher.refreshToken() }
}
}
Add a token refresher into koin module.
val networkModule = module {
single<TokenRefresher> {
object : TokenRefresher {
// now use the userApi
override fun refreshToken() {
val userApi: UserApi = get()
userApi.refreshToken()
}
}
}
single<UserApi> {
Retrofit.Builder()
.client(get(named("httpClient")))
.baseUrl(get<String>(named("...")))
.addConverterFactory(
...
)
.build()
.create(UserApi::class.java)
}
single(named("httpClient")) {
val tokenAuthenticator: TokenAuthenticator = get()
OkHttpClient.Builder()
.authenticator(tokenAuthenticator)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
}
single {
TokenAuthenticator(get())
}
}
Hope it helps.
I'm receiving a response from BE and the response is a base64 encoded image. The response looks like this:
{"image":"/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/ ...} (whole response here: https://pastebin.com/ViFTAhRw)
Looks like a property named image followed by a string. So I've created my model class:
#JsonClass(generateAdapter = true)
data class ApiBase64Image(
#field:Json(name = "image") val imageString: String?
) {
fun toDomain(): Base64Image {
return Base64Image(imageString.orEmpty())
}
}
And finally, my DI object:
#Module
#InstallIn(SingletonComponent::class)
object ApiModule {
#Provides
#Singleton
fun provideApi(builder: Retrofit.Builder): MyApi {
return builder
.build()
.create(MyApi::class.java)
}
#Provides
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit.Builder {
return Retrofit.Builder()
.baseUrl(ApiConstants.BASE_ENDPOINT)
.client(okHttpClient)
.addConverterFactory(MoshiConverterFactory.create())
}
#Provides
fun provideOkHttpClient(
authenticationInterceptor: AuthenticationInterceptor
): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(authenticationInterceptor)
.build()
}
}
This code, however, does not work as I'm receiving errors:
Unable to create converter for class ... .ApiBase64Image
Failed to find the generated JsonAdapter class for class ... .ApiBase64Image
I'm not sure what is giving Moshi problems. Is it the data class serialization? Or my DI setup? Or something else entirely?
#JsonClass(generateAdapter = true)
This is used by Codegen, so you need to add that dependency :
plugins {
id("com.google.devtools.ksp").version("1.6.10-1.0.4") // Or latest version of KSP
}
dependencies {
ksp("com.squareup.moshi:moshi-kotlin-codegen:1.13.0")
}
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.
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) }
}
Help solve the problem!(((I have 3 modules for DI. There is a retrofit object in natworkModule, all viewModels in viewModelModule, and all requests to the server in respositoryModule. I did everything according to the documentation, but I cannot find this error in Google. Thank you in advance!!! Sorry for my english!)
class App : Application(){
override fun onCreate() {
super.onCreate()
startKoin(this, listOf(natworkModule, viewModelModule,repositoryModule))
}
}
var natworkModule = module {
single { createOkHttpClient() }
single { createApiService<ApiService>(get () ,getProperty(SERVER_URL))
}
}
const val SERVER_URL = "https://api.github.com/"
fun createOkHttpClient() : OkHttpClient{
val httpLoggingInterceptor = HttpLoggingInterceptor()
httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BASIC
return OkHttpClient.Builder()
.connectTimeout(60L, TimeUnit.SECONDS)
.readTimeout(60L, TimeUnit.SECONDS)
.addInterceptor(httpLoggingInterceptor).build()
}
inline fun <reified T> createApiService(okHttpClient: OkHttpClient, url: String): T {
val retrofit = Retrofit.Builder()
.baseUrl(url)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(LiveDataCallAdapterFactory()).build()
return retrofit.create(T::class.java)
}
var repositoryModule = module {
factory<TestRepository> {
TestRepositoryImpl(get())
}
}
var viewModelModule = module {
viewModel {
TestViewModel(get())
}
}
Problem was in this constant value ->
SERVER_URL = "https://api.github.com/"
Koin could not find it. Therefore there was an exception. Thanks to all!!!