Kotlin Android Retrofit 2.6.0 with coroutines error handling

后端 未结 7 2034
粉色の甜心
粉色の甜心 2021-01-30 23:09

I am using Retrofit 2.6.0 with coroutines for my web service call. I am getting the API response properly with all response codes (success & error cases). My Issue is when I

相关标签:
7条回答
  • 2021-01-30 23:34

    You can just add a CoroutineExceptionHandler to handle the error for you:

    In your ViewModel:

    val coroutineExceptionHandler = CoroutineExceptionHandler{_, t -> {
      t.printStackTrace()
      showErrorOrSomething()
    }}
    
    viewModelScope.launch(Dispatchers.IO + coroutineExceptionHandler) {
       val apiResponse = ApiConnectionBridge.getHomeUiDetails(SharedPrefUtils.getAuthToken(context))
            if (apiResponse.isSuccessful) {
               // success case
            } else {
                // error case
            }
    }
    
    0 讨论(0)
  • 2021-01-30 23:36

    you could do the following in your ViewModel to gracefully handle exceptions:

    viewModelScope.launch {
       kotlin.runCatching {
          withContext(Dispatchers.IO){
              ApiConnectionBridge.getHomeUiDetails(SharedPrefUtils.getAuthToken(context))
          }
    
      }.onSuccess { 
           // do something with success response
      }.onFailure{
          // do something on failure response
      }
    
    }
    
    0 讨论(0)
  • 2021-01-30 23:44

    I am a bit late to the party but I think this is the finest solution:

    If you are using Kotlin + Retrofit + Coroutines then just use try and catch for network operations like,

    viewModelScope.launch(Dispatchers.IO) {
            try {
                val userListResponseModel = apiEndPointsInterface.usersList()
                returnusersList(userListResponseModel)
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    

    Where, Exception is type of kotlin and not of java.lang

    This will handle every exception like,

    1. HttpException
    2. SocketTimeoutException
    3. FATAL EXCEPTION: DefaultDispatcher etc

    Here is my usersList() function

    @GET(AppConstants.APIEndPoints.HOME_CONTENT)
    suspend fun usersList(): UserListResponseModel
    

    Note: Your RetrofitClient Class must have this as client

    OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)
                .readTimeout(10, TimeUnit.SECONDS)
                .writeTimeout(10, TimeUnit.SECONDS)
    
    0 讨论(0)
  • 2021-01-30 23:45

    An addition to Arthur Matsegor's answer:

    In my case API returns to me an error message for bad requests. For this scenario, i need to catch error message on Catch function. I know, writing try/catch to Catch function looks like ugly but it's worked.

    private suspend fun <T : Any> handleRequest(requestFunc: suspend () -> T): Result<T> {
        return try {
            Result.success(requestFunc.invoke())
        } catch (httpException: HttpException) {
            val errorMessage = getErrorMessageFromGenericResponse(httpException)
            if (errorMessage.isNullOrBlank()) {
                Result.failure(httpException)
            } else {
                Result.failure(Throwable(errorMessage))
            }
        }
    }
    
    private fun getErrorMessageFromGenericResponse(httpException: HttpException): String? {
        var errorMessage: String? = null
        try {
            val body = httpException.response()?.errorBody()
            val adapter = Gson().getAdapter(GenericResponse::class.java)
            val errorParser = adapter.fromJson(body?.string())
            errorMessage = errorParser.errorMessage?.get(0)
        } catch (e: IOException) {
            e.printStackTrace()
        } finally {
            return errorMessage
        }
    }
    
    0 讨论(0)
  • 2021-01-30 23:46

    Well, that's what I do, just to reduce try-catch junk copypaste

    Declare our API call methods like this

    @GET("do/smth")
    suspend fun doSomething(): SomeCustomResponse
    

    In a separate file

    suspend fun <T: Any> handleRequest(requestFunc: suspend () -> T): kotlin.Result<T> {
        return try {
            Result.success(requestFunc.invoke())
        } catch (he: HttpException) {
            Result.failure(he)
        }
    }
    

    Usage:

    suspend fun doSmth(): kotlin.Result<SomeCustomResponse> {
        return handleRequest { myApi.doSomething() }
    }
    

    HTTP codes are handled by Retrofit - it just throws an HttpException if responseCode is not 2xx. So what we should do is just catch this exception.

    I know, it is not a perfect solution, but let's for Jake to invent something better)

    0 讨论(0)
  • 2021-01-30 23:46

    Maybe it helps somebody: Its possible to get rid of the SocketTimeoutException in the following way: 1. Set the readTimeout of your client to a arbitrary number, here its 2s

     val client = OkHttpClient.Builder()
                .connectTimeout(1, TimeUnit.SECONDS)
                .readTimeout(2, TimeUnit.SECONDS).build()
    

    2. When doing API calls always wrap them inside a coroutine timeout.

      try {
              withTimeout(1000) {
                  try {
                      val retrivedTodo = APICall()
                      emit(retrivedTodo)
                  } catch (exception: HttpException) {
                      exception.printStackTrace()
                  }
              }
          }catch (ex: CancellationException) {
            Log.e("timeout","TimeOut")
          }
    

    The main point is, that the withTimeout value is smaller than the Retrofit timeout value. This assures, that the coroutine stops being suspended BEFORE the Retrofit timeout kicks in.

    Anyway, this is produces many try/catch blocks and is probably not what the retrofit developers wanted, when including the coroutine support.

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