top of page

Retrofit: Setup Timeout Dynamically

Retrofit is a wrapper library of HTTP client. Timeout has to be set to HTTP client. Retrofit doesn't know anything about timeouts. But we can set timeout to HTTP client using Retrofit interceptors. This requires OkHttp 3.9.0 (or newer).



Some API need more time to compete network request than usual time. Usually Post & GET requests containing image or other large object, spend much time. If we set timeout time dynamically on runtime before executing the request, it may create better user experience.




Initially we can set timeout in API class and HTTP client gets updated while intercepting.


interface LoginApi {
    /**
     * Login
     *
     * @param username
     * @param password
     * @return
     */
    @Headers("CONNECT_TIMEOUT:30000", "READ_TIMEOUT:30000", "WRITE_TIMEOUT:30000")
    @POST("login")
    fun login(@Query("username") username: String, @Query("password") password: String): Observable<ResponseBody>

    //Default timeout 
    fun getUser(@Query("clientId") clientId: String): Observable<ResponseBody>
}


Default timeout will work for those requests where no timeout set dynamically. Two interceptors work for completing the whole process. The 1st interceptor help to set the timeouts and the 2nd one remove the timeouts from header. Actually the header caches times temporarily until set to HTTP client through interceptor.


fun getRetrofitClient(): Retrofit {

    val okHttpClient = OkHttpClient.Builder()
            .connectTimeout(DEFAULT_CONNECT_TIMEOUT, TimeUnit.MICROSECONDS)
            .readTimeout(DEFAULT_READ_TIMEOUT, TimeUnit.MICROSECONDS)
            .writeTimeout(DEFAULT_WRITE_TIMEOUT, TimeUnit.MICROSECONDS)
            .addInterceptor(interceptor)
            .addNetworkInterceptor(networkInterceptor)
            .build()

    return Retrofit.Builder()
            .baseUrl(BASE_URL)
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
}


The interceptor set timeout dynamically to HTTP client before executing network request.


private val interceptor = object : Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()

        var connectTimeout = chain.connectTimeoutMillis()
        var readTimeout = chain.readTimeoutMillis()
        var writeTimeout = chain.writeTimeoutMillis()

        val connect = request.header(CONNECT_TIMEOUT)
        val read = request.header(READ_TIMEOUT)
        val write = request.header(WRITE_TIMEOUT)

        if (!TextUtils.isEmpty(connect)) {
            connectTimeout = connect!!.toInt()
        }

        if (!TextUtils.isEmpty(read)) {
            readTimeout = read!!.toInt()
        }

        if (!TextUtils.isEmpty(write)) {
            writeTimeout = write!!.toInt()
        }

        return chain.withConnectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
                .withReadTimeout(readTimeout, TimeUnit.MILLISECONDS)
                .withWriteTimeout(writeTimeout, TimeUnit.MILLISECONDS)
                .proceed(request)
    }
}



The networkInterceptor removes the timeouts from header that are already set to HTTP client. The order of setting interceptors has to be restraint. The networkInterceptor has to set after setting 1st interceptor, otherwise timeouts get removed before setting to HTTP client.

private val networkInterceptor = object : Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        val requestBuilder = request.newBuilder()

        if (!TextUtils.isEmpty(request.header(CONNECT_TIMEOUT))) {
            requestBuilder.removeHeader(CONNECT_TIMEOUT)
        }

        if (!TextUtils.isEmpty(request.header(READ_TIMEOUT))) {
            requestBuilder.removeHeader(READ_TIMEOUT)
        }

        if (!TextUtils.isEmpty(request.header(WRITE_TIMEOUT))) {
            requestBuilder.removeHeader(WRITE_TIMEOUT)
        }

        return chain.proceed(requestBuilder.build())
    }
}

I hope the article helps you and make easy to setup timeout dynamically using retrofit library. Please let me know if you feel any difficulties.


Comments


bottom of page