Home > Net >  Android - cannot inject Interface class into module
Android - cannot inject Interface class into module

Time:08-05

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)

CodePudding user response:

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.

  • Related