Dynamic urls with Koin ans Retrofit

Viewed 2917

Using Retrofit for network calls and Koin for dependency injection in an Android app, how to support dynamic url change?

(while using the app, users can switch to another server)

EDIT: network module is declared like this:

fun networkModule(baseUrl: String) = module {

    single<Api> {

        Retrofit.Builder()
                .baseUrl(baseUrl) 
                .client(OkHttpClient.Builder().readTimeout(30, TimeUnit.SECONDS)
                        .connectTimeout(30, TimeUnit.SECONDS)
                        .writeTimeout(30, TimeUnit.SECONDS)
                        .build())
                .build().create(Api::class.java)
    }

I am starting Koin in the Aplication class onCreate like this:

 startKoin {

        if (BuildConfig.DEBUG) AndroidLogger() else EmptyLogger()

        androidContext(this@App)

        modules(listOf(networkModule(TEST_API_BASE_URL), storageModule, integrationsModule, appModule))
    }
2 Answers

I faced the same problem recently. The most convenient way is to use a Interceptor to change the baseUrl dynamically.

class HostSelectionInterceptor(defaultHost: String? = null, defaultPort: Int? = null) : Interceptor {
    @Volatile var host: String? = null
    @Volatile var port: Int? = null

    init {
        host = defaultHost
        port = defaultPort
    }

    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain): okhttp3.Response {
        var request = chain.request()

        this.host?.let {host->
            val urlBuilder = request.url().newBuilder()

            urlBuilder.host(host)

            this.port?.let {
                urlBuilder.port(it)
            }
            request = request.newBuilder().url(urlBuilder.build()).build()
        }

        return chain.proceed(request)
    }
}

Initialize it with your default url.

single { HostSelectionInterceptor(HttpUrl.parse(AppModuleProperties.baseUrl)?.host()) }
single { createOkHttpClient(interceptors = listOf(get<HostSelectionInterceptor>()))}

And add this interceptor when creating your OkHttpClient.

val builder = OkHttpClient().newBuilder()    
interceptors?.forEach { builder.addInterceptor(it) }

To change the url you only have to update the interceptors member.

fun baseUrlChanged(baseUrl: String) {
    val hostSelectionInterceptor = get<HostSelectionInterceptor>()
    hostSelectionInterceptor.host = baseUrl
}

I've tried with Koin loading/unloading modules..and for a short period of time it worked, but later, after a minimal change I wasn't able to make it reload again.

At the end, I solved it with wrapper object:

class DynamicRetrofit(private val gson: Gson) {

private fun buildClient() = OkHttpClient.Builder()
        .build()

private var baseUrl = "https://etc..." //default url

private fun buildApi() = Retrofit.Builder()
        .baseUrl(baseUrl)
        .addConverterFactory(GsonConverterFactory.create(gson))
        .client(buildClient())
        .build().create(MyApi::class.java)

var api: MyApi = buildApi()
    private set

fun setUrl(url: String) {
    if (baseUrl != url)
        baseUrl = url

    api = buildApi()
}}

I declare it in within Koin module like this:

  single<DynamicRetrofit>()
    {
        DynamicRetrofit(get(), get())
    }

and use it in pretty standard way:

dynamicRetrofit.api.makeSomeRequest()

It was good solution for my case since I change baseUrl very rarely. If you need to make often and parallel calls to two different servers it will probably be inefficient since you this will recreate HTTP client often.

Related