Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handle no internet connection error of retrofit 2.6 with kotlin coroutines

I'm using retrofit 2.6 with kotlin coroutines to make API call without block the UI thread, I got it work but the app crashes when I switch off the internet connection. The logcat error is: E/AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-1

Here is my code:

private fun handleIntent(slug: String) {
    val service = UtilityMethods.migrationTimeService()

    UtilityMethods.showView(loading_view)
    UtilityMethods.hideView(network_error_msg)

    CoroutineScope(Dispatchers.IO).launch {
        val res = service.getPostBySlug(slug)

            try {
                withContext(Dispatchers.Main) {

                    //Do something with response e.g show to the UI.
                    val post = res.body()!!.first()

                    UtilityMethods.hideView(loading_view)

                    val title = post.title?.rendered
                    val content = post.content?.rendered
                    val imageUrl = post.jetPackFeaturedMediaUrl

                    title_txtView.text = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
                        Html.fromHtml(title, Html.FROM_HTML_MODE_COMPACT).toString()
                    else
                        Html.fromHtml(title).toString()

                    content_txtView.loadData(content.toString(), "text/html", "UTF-8")

                    Picasso.get().load(imageUrl).fit().centerCrop().into(thumbnail_imgview)
                }

            } catch (e: HttpException) {
                UtilityMethods.showView(network_error_msg)
            } catch (e: Throwable) {
                Toast.makeText(this@PostContentActivity, "Ooops: Something else went wrong", Toast.LENGTH_LONG)
            }
    }
}
like image 777
Mustapha Ojo Avatar asked Jan 27 '23 04:01

Mustapha Ojo


2 Answers

I've got the code working, the new code is:

private fun handleIntent(slug: String) = GlobalScope.launch(Dispatchers.Main) {
    val service = UtilityMethods.migrationTimeService()

    UtilityMethods.showView(loading_view)
    UtilityMethods.hideView(network_error_msg)

    try {
        val res = withContext(Dispatchers.IO) {
            service.getPostBySlug(slug)
        }

        //Do something with response e.g show to the UI.
        val post = res.body()!!.first()

        UtilityMethods.hideView(loading_view)

        val title = post.title?.rendered
        val content = post.content?.rendered
        val imageUrl = post.jetPackFeaturedMediaUrl

        title_txtView.text = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
            Html.fromHtml(title, Html.FROM_HTML_MODE_COMPACT).toString()
        else
            Html.fromHtml(title).toString()

        content_txtView.loadData(content.toString(), "text/html", "UTF-8")

        Picasso.get().load(imageUrl).fit().centerCrop().into(thumbnail_imgview)
    }
    catch (e: HttpException) {
        Toast.makeText(this@PostContentActivity, "Exception ${e.message}", Toast.LENGTH_LONG).show()
    }catch (e: IOException) {
        UtilityMethods.hideView(loading_view)
        UtilityMethods.showView(network_error_msg)
    } catch (e: Throwable) {
        Toast.makeText(this@PostContentActivity, "Ooops: Something else went wrong ${e.message}", Toast.LENGTH_LONG).show()
    }
}
like image 69
Mustapha Ojo Avatar answered Feb 04 '23 02:02

Mustapha Ojo


So while looking into stacktrace I found that ConnectException is thrown when network is unavailable

And that's how I do it in kotlin and it works for me,

suspend fun<T: Any> safeAPICall(call: suspend () -> Response<T>) : T{
val response = try {
    call.invoke()
}
catch (e:java.lang.Exception){
    e.printStackTrace()
    val message = if( e is ConnectException) "Connection Error" else "Something went wrong. Please try again."
    throw IOException(ResponseError(message, 500).convertToJsonString())
}


// When connection is OK

if(response.isSuccessful){
    return response.body()!!
}else{
    val error = response.errorBody()?.string()

    error?.let{
        val message = JSONObject(it).optString("message", "Something went wrong")
        val responseError = ResponseError(message, response.code())
        throw IOException(responseError.convertToJsonString())

    }
    throw IOException(ResponseError("Something went wrong. Please try again.", 500).convertToJsonString())
}
}

The data class that I use

data class ResponseError(val message:String, val errorCode:Int)

Usage:

try {
      val response = safeAPICall {APIClient.planner.viewSites(view.context.authToken)}
 }
 catch (e:Exception){
    view.snack(e.message?.toModel<ResponseError>()?.message?: unspecified_error)
 }

Bonus:

 inline fun <reified T> JSONObject.toModel(): T? = this.run {
   try {
       Gson().fromJson<T>(this.toString(), T::class.java)
   }
   catch (e:java.lang.Exception){ e.printStackTrace(); null }
}


inline fun <reified T> String.toModel(): T? = this.run {
   try {
      JSONObject(this).toModel<T>()
    }
   catch (e:java.lang.Exception){  null }
}
like image 42
Aziz Avatar answered Feb 04 '23 03:02

Aziz