How to use dynamic urls at Runtime with Retrofit 2?

serap bercin
3 min readJul 23, 2018

--

This article about handle dynamic urls with Retrofit 2. Almost all applications include this library and for some cases we need to change url at runtime or need backup url(s) when it occur any exception(socket time out, retry or etc). Also user needs to change url in setting screen somehow. For these cases, how we can handle it? I’ll use that with Retrofit, Kotlin Coroutines and Dagger to handle dynamic url.

We already know that manipulate endpoint with retrofit when specify the endpoints via http method such as get, post, put or delete. Retrofit also provide these annotations for these operations. However, we need to change base url at runtime. Let’s start to add dependencies.

Setup gradle:

//retrofit 2
implementation 'com.squareup.retrofit2:retrofit:2.4.0'
implementation 'com.squareup.retrofit2:converter-gson:2.4.0'

//OkHttp
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'

//Coroutines for adapter
implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-experimental-adapter:1.0.0'
  • If you want to see okhttp logs, you can simply add logging interceptor dependency in gradle.

I created an ApiModule which is singleton and should create at once. After that I added host and scheme fields to able to change scheme and host dynamically. We will use this methods when call api service to want to change url.

@Module
object ApiModule {

private var scheme: String = ""
set(url) {
field = HttpUrl.parse(url)!!.scheme()
}


private var host: String = ""
set(url) {
field = HttpUrl.parse(url)!!.host()
}

}

I selected coroutines for addCallAdapterFactory when create retrofit builder. But it doesn’t matter, you can choose RxJava2CallAdapterFactory or etc.

@JvmStatic
@Singleton
@Provides
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit.Builder {

val okHttpBuilder = okHttpClient.newBuilder()
val httpLoggingInterceptor = HttpLoggingInterceptor()
httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
okHttpBuilder.addInterceptor(httpLoggingInterceptor)


return Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.client(okHttpBuilder.build())
.baseUrl("http://localhost/")

}

We built retrofit builder as above code snippet, important point is baseUrl(). passed “http://localhost/" instead of our default url. In this way it will ask url for every request but we need to provide OkHttpClient for this case.

When we provide our service method, we should passed Retrofit.Builder, this code snippet is like;

@JvmStatic
@Provides
@Singleton
fun provideExampleService(retrofit: Retrofit.Builder): ExampleService = retrofit.build().create(ExampleService::class.java)

Also, I added api method in interface which already provided in ApiModule.

interface ExampleService {

@GET("list/")
fun getList(): Deferred<List>

}

ApiModule final state is roughly:

import com.jakewharton.retrofit2.adapter.kotlin.coroutines.experimental.CoroutineCallAdapterFactory
import dagger.Module
import dagger.Provides
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import javax.inject.Singleton

@Module
object ApiModule {

var scheme: String = ""
set(url) {
field = HttpUrl.parse(url)!!.scheme()
}


var host: String = ""
set(url) {
field = HttpUrl.parse(url)!!.host()
}


@JvmStatic
@Singleton
@Provides
fun provideOkHttpClient() =
OkHttpClient.Builder()
.addInterceptor {
val request = it.request()

val newUrl: HttpUrl?
newUrl = when {
scheme != null && host != null -> request.url().newBuilder()
.scheme(scheme)
.host(host)
.addQueryParameter("apikey", "something")
.build()
else -> request.url().newBuilder()
.addQueryParameter("apikey", "something")
.build()
}

it.proceed(
request.newBuilder()
.url(newUrl)
.build())

}
.build()!!


@JvmStatic
@Singleton
@Provides
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit.Builder {

val okHttpBuilder = okHttpClient.newBuilder()
val httpLoggingInterceptor = HttpLoggingInterceptor()
httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
okHttpBuilder.addInterceptor(httpLoggingInterceptor)


return Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.client(okHttpBuilder.build())
.baseUrl("http://localhost/")

}

@JvmStatic
@Provides
@Singleton
fun provideExampleService(retrofit: Retrofit.Builder): ExampleService = retrofit.build().create(ExampleService::class.java)

}

Everything is ready, after this we can simply change url for every request.

For instance: We can call getList method however if it need to change url, we should set host and scheme like this or maybe you need to set when failure case, retry. it depends on your case.

suspend fun getList(): Deferred<List> = withContext(context) {   //set the our host and scheme
//if you need to change url
ApiModule.host = "yourHost"
ApiModule.scheme = "yourScheme"

exampleService.getList()
}
fun register() = launch(uiContext, parent = job) {
val result = getList.await()
onSuccess(result)
job.join()

}

Consequently, we simply manipulated the host and scheme at runtime whenever you need to change.

--

--