Issue
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)
Solution
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.
Answered By - ocos
Answer Checked By - Willingham (JavaFixing Volunteer)