Retrofit 2: Catch connection timeout exception

后端 未结 7 1175
执念已碎
执念已碎 2020-11-27 03:26

I have the following setup:

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


        
相关标签:
7条回答
  • 2020-11-27 04:25

    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.

    0 讨论(0)
提交回复
热议问题