Retrofit 2: Catch connection timeout exception

AndroidRetrofitConnection TimeoutOkhttp

Android Problem Overview


I have the following setup:

final OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setReadTimeout(5, TimeUnit.SECONDS);
okHttpClient.setConnectTimeout(5, TimeUnit.SECONDS);

RestAdapter.Builder builder = new RestAdapter.Builder()
        .setEndpoint(ROOT)
        .setClient(new OkClient(okHttpClient))
        .setLogLevel(RestAdapter.LogLevel.FULL);

I am trying to handle the situation in which my server is down and the user gets a connection timeout exception, this is my logging:

java.net.SocketTimeoutException: failed to connect to /192.168.0.53 (port 3000) after 5000ms

Full logging: http://pastebin.com/gscCGb7x

Is there a way to route this into the retrofit failure method so I can handle it over there?

Thanks in advance!

Android Solutions


Solution 1 - Android

For Retrofit 2

Define a listener in your web service instance:

public interface OnConnectionTimeoutListener {
    void onConnectionTimeout();
}

Add an interceptor to your web service:

public WebServiceClient() {
    OkHttpClient client = new OkHttpClient();
    client.setConnectTimeout(10, TimeUnit.SECONDS);
    client.setReadTimeout(30, TimeUnit.SECONDS);
    client.interceptors().add(new Interceptor() {
        @Override
        public Response intercept(Chain chain) throws IOException {
            return onOnIntercept(chain);
        }
    });
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .client(client)
            .build();
    webService = retrofit.create(WebService.class);
}

Enclose your intercep code with try-catch block and notify listener when exception happens:

private Response onOnIntercept(Chain chain) throws IOException {
    try {
        Response response = chain.proceed(chain.request());
        String content = UtilityMethods.convertResponseToString(response);
        Log.d(TAG, lastCalledMethodName + " - " + content);
        return response.newBuilder().body(ResponseBody.create(response.body().contentType(), content)).build();
    }
    catch (SocketTimeoutException exception) {
        exception.printStackTrace();
        if(listener != null)
            listener.onConnectionTimeout();
    }

    return chain.proceed(chain.request());
}

Solution 2 - Android

@Override
public void onFailure(Call call, Throwable t) {
    if(t instanceof SocketTimeoutException){
        message = "Socket Time out. Please try again.";
    }
}

Solution 3 - Android

In case someone come here with Kotlin/Coroutines facing the same issue, add an error handler to your coroutines scope:

CoroutineScope(Dispatchers.IO).launch(handler) {

while the handler by itself looks like:

val handler = CoroutineExceptionHandler { _, exception ->
    Log.t("Network", "Caught $exception")
}

Solution 4 - Android

None of the answers quite worked for me but they led me in the right direction. See below how I did it in Kotlin.

You can either throw the exceptions in the ErrorInterceptor and catch them in your api call function:

class ErrorInterceptor : Interceptor {

    override fun intercept(chain: Chain): Response {

        val request = chain.request()

        try {
            val response = chain.proceed(request)
            val bodyString = response.body!!.string()

            return response.newBuilder()
                .body(bodyString.toResponseBody(response.body?.contentType()))
                .build()
        } catch (e: Exception) {
            when (e) {
                is SocketTimeoutException -> {
                    throw SocketTimeoutException()
                }

               // Add additional errors... //

            }
        }
    }

Or bundle exceptions with a response object; something like this:

class ErrorInterceptor : Interceptor {

    override fun intercept(chain: Chain): Response {

        val request = chain.request()

        try {
            val response = chain.proceed(request)
            val bodyString = response.body!!.string()

            return response.newBuilder()
                .body(bodyString.toResponseBody(response.body?.contentType()))
                .build()
        } catch (e: Exception) {
            var msg = ""
            val interceptorCode: Int

            when (e) {
                is SocketTimeoutException -> {

                    msg = "Socket timeout error"
                    interceptorCode = 408

                }

               // Add additional errors... //

            }

             return Response.Builder()
                .request(request)
                .protocol(Protocol.HTTP_1_1)
                .code(interceptorCode)
                .message(msg)
                .body("{${e}}".toResponseBody(null)).build()
        }
    }
}

Add the ErrorInterceptor to your okHttpClient:

okHttpClient.newBuilder()
                .addInterceptor(ErrorInterceptor())
                .connectTimeout(10, TimeUnit.SECONDS)
                 // ... //
                .build()

And then something like this in your repository layer:

suspend fun makeAPIRequest(): Resource<ApiResponse> {

        return withContext(ioDispatcher) {

            var response: Response<ApiResponse>? = null

            try {
                response = getResponse()

                // Do additional ops on response here //

            } catch (e: Exception) {

                // Exceptions thrown in ErrorInterceptor will propagate here

            }
        }
    }

Solution 5 - Android

Apparently the exception does get wrapped into a RetrofitException so you can handle it in the failure method.

Solution 6 - Android

It's a bit more complicated. With Retrofit you can make API calls that are either synchronous or asynchronous.

If your endpoint returns void and has a callback it is asynchronous. If it returns something and has no callback it's synchronous.

For asynchronous calls you get this exception in the onFailure(...) method of your callback.

For synchronous calls you don't get it at all, unless you wrap your call in a try/catch.

try {
   // your synchronous call goes here  
} catch (RetrofitError error) {
   // handle errors
}

Update: The above answer applies to Retrofit 1.9. Retrofit 2.0 has changed this a lot. If you're wondering about how things now work in Retrofit 2.0, this article gives some pointers http://inthecheesefactory.com/blog/retrofit-2.0/en

Solution 7 - Android

I'm posting this for two reasons:

  1. I personnaly tried increasing connection timeout but, evetually, it doesn't really solve the problem at its root. Besides, user is not supposed to wait for longer than 10 seconds according to this post.
  2. In a real world application, we would rather do our best to implement a solution in as clean as possible way.

So, here's a solution that I came up with in Kotlin. It's inspired from the answer provided by @Olcay Ertaş and combined with Google's recommended architecture for Android apps.

  1. Create a TimeoutInterceptor interface:

     interface TimeoutInterceptor : Interceptor
    
  2. Implement the TimeoutInterceptor interface:

     class TimeoutInterceptorImpl : TimeoutInterceptor {
    
     	override fun intercept(chain: Interceptor.Chain): Response {
     		if (isConnectionTimedOut(chain))
     			throw SocketTimeoutException()
     		return chain.proceed(chain.request())
     	}
    
     	private fun isConnectionTimedOut(chain: Interceptor.Chain): Boolean {
     		try {
     			val response = chain.proceed(chain.request())
     			val content = response.toString()
     			response.close()
     			Log.d(tag, "isConnectionTimedOut() => $content")
     		} catch (e: SocketTimeoutException) {
     			return true
     		}
     		return false
     	}
     }
    
  3. In your ApiService interface, add the TimeoutInterceptor to the OkHttpClient builder:

     val okHttpClient = OkHttpClient.Builder()
     		.addInterceptor(requestInterceptor)
             // Add timeout interceptor
     		.addInterceptor(timeoutInterceptor)
             // Set a 5s custom connect timout
             .connectTimeout(5, TimeUnit.SECONDS)
     		.build()
    

As you might have noticed, you can set a custom connect timeout. Otherwise, it's left to 10 seconds as a default value according to the documentation.

  1. Create an enum class ConnectionState. It will provide an enum constant object CONNECTION_TIMEOUT which will be used further to convey the appropriate connection (or API call) state from EntityNetworkDataSource class to the View class (if you follow Google's MVVM pattern):

     enum class ConnectionState {
     	CONNECTED, NOT_CONNECTED, CONNECTION_TIMEOUT
     }
    
  2. Assuming your EntityNetworkDataSource interface would look something like this:

     interface EntityNetworkDataSource {
     	val fetchedEntity: LiveData<Entity>
    
         // Wrap your ConnectionState object in LiveData in order to be able to observe it in the View
     	val connectionState: LiveData<ConnectionState>
     	
         // Fetch `Entity` object from the network
     	suspend fun fetchEntity(id: Int)
     }
    
  3. In the EntityNetworkDataSource implementation class, you can properly catch the SocketTimeoutException as shown below, inside the fetchEntity(id: Int) implementation:

     class EntityNetworkDataSourceImpl(
     		private val apiService: ApiService
     ) : EntityNetworkDataSource {
     	
     	private val _fetchedEntity = MutableLiveData<Entity>()
    
     	override val fetchedEntity: LiveData<Entity>
     		get() = _fetchedEntity
    
         // We want to keep our MutableLiveData private because they can be changed.
         // So we want to be able to update them only from the inside of this class
     	private val _connectionState = MutableLiveData<ConnectionState>()
    
     	override val connectionState: LiveData<ConnectionState>
     		get() = _connectionState
    
     	override suspend fun fetchEntity(id: Int) {
     		try {
     			val fetchedEntity = apiService
     					.getEntity(id)
     					.await()
    
                 // Convey the updated connection state to the observer View
     			_connectionState.postValue(ConnectionState.CONNECTED)
    
     			_fetchedEntity.postValue(fetchedEntity)
     		} catch (e: SocketTimeoutException) {
     			Log.e(tag, "Connection timeout. ", e)
                 // Catch the SocketTimeoutException and post the updated connection state to the observer View
     			_connectionState.postValue(ConnectionState.CONNECTION_TIMEOUT)
     		}
     	}
     }
    

The same principle applies to any connection exception you wana intercept and catch.

Solution 8 - Android

Kotlin

If you want to use Retrofit in Kotlin follow below steps:

Define your Retrofit interface:

interface GitHubApi {
    
    @GET("/users/{userName}/repos")
    fun repos(@Path("userName") userName: String): Call<List<Repo>>
}

Implement your service:

class Api(...) {

    private val baseUrl = "https://api.github.com"
    private val api: GitHubApi

    private fun loggingInterceptor(...): HttpLoggingInterceptor {...}

    private fun okHttpBuilder(): OkHttpClient {...}

    init {...}

    fun repos(
        userName: String,
        onSuccess: (list: List<Repo>?) -> Unit,
        onFailure: (message: String?) -> Unit): Future<Unit> {
        return runAsync(api.repos(userName), onSuccess, onFailure)
    }

    private fun <T> runAsync(
        call: retrofit2.Call<T>,
        onSuccess: (T?) -> Unit,
        onFailure: (message: String?) -> Unit) : Future<Unit> {
        return doAsync {
            try {
                val response = call.execute()
                when {
                    response.isSuccessful -> response.body()?.let {
                        onSuccess(it)
                    }
                    else -> {
                        onFailure(response.raw().message())
                    }
                }
            } catch (e: IOException) {
                if (e is SocketTimeoutException) {
                    onFailure("Response time out!")
                } else {
                    onFailure(e.message)
                }
            }
        }
    }
}

Call your service in where you want:

Api().repos("olcayertas",
    onSuccess = {
        Log.d("MainActivity", "Response:\n" + toJson(it))
    },
    onFailure = {
        Log.e("MainActivity", "Error: $it")
    })

You can handle any exception you want in runAsync function.

You can get fully working example here.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJdruweView Question on Stackoverflow
Solution 1 - AndroidOlcay ErtaşView Answer on Stackoverflow
Solution 2 - AndroidBhavya VView Answer on Stackoverflow
Solution 3 - Androidcareful7jView Answer on Stackoverflow
Solution 4 - AndroidSVPView Answer on Stackoverflow
Solution 5 - AndroidJdruweView Answer on Stackoverflow
Solution 6 - AndroidEspen RiskedalView Answer on Stackoverflow
Solution 7 - AndroidAndroWeedView Answer on Stackoverflow
Solution 8 - AndroidOlcay ErtaşView Answer on Stackoverflow