I have the following setup:
final OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setReadTimeout(5, TimeUnit.SECONDS);
okHttpClient.setConnectTi
I'm posting this for two reasons:
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.
Create a TimeoutInterceptor
interface:
interface TimeoutInterceptor : Interceptor
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
}
}
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.
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
}
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)
}
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.