5

I'm very confused about how Exception handling works with coroutines.

I was hoping that it would be possible to have a chain of suspend functions that would pass Exceptions between themselves like synchronous code. So if say Retrofit threw an IOException, I could handle that exception at the beginning of the chain of suspend functions such as in a presenter to show an error to a user.

I made this simple example to try out coroutines but if I uncomment either throw Exception call the code after the Exception fails to run but the Exception does not crash the app.

package com.example.myapplication

import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.widget.Button
import android.widget.TextView
import kotlinx.coroutines.experimental.delay
import kotlinx.coroutines.experimental.launch

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val text = findViewById<TextView>(R.id.thing_text)
        val button = findViewById<Button>(R.id.thing_button)

        var count = 0

        button.setOnClickListener {
            launch {
                count++
//                throw Exception("Boom")
                val string = delayedStringOfInt(count)
                runOnUiThread { text.text = string }
            }
        }
    }

    suspend fun delayedStringOfInt(int: Int): String {
        delay(1000)
//        throw Exception("Boom")
        return int.toString()
    }
}

I have tried using async and CoroutineExceptionHandler.

TTransmit
  • 3,270
  • 2
  • 28
  • 43

2 Answers2

2

When using async, you should await the result somewhere so you don't lose any exceptions.

Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
  • 1
    I had tried `async` with `await` before but it complains that `Suspend function await should only be called from a coroutine or another suspend function.` However, wrapping it all in `runBlocking` works without disrupting the asynchronous running of the code. `runBlocking` is a confusing name as I was expecting it to block until the coroutines inside completed. In my case meaning that if you rapidly clicked the buttonI expected that you would get the number incrementing every second rather than rapidly. I'll mark this as answer but put my fixed code in another answer. – TTransmit Dec 13 '17 at 08:13
  • Looking more closely, I can see that `runBlocking` is blocking the UI thread in this case, which is not what I want. I'll have to try using a callback. – TTransmit Dec 13 '17 at 08:30
  • Using the kotlinx `Continuation` here is just giving me ugly callback hell so far. – TTransmit Dec 13 '17 at 08:51
1

Here is code that catches the exception based on Alexey Romanov's answer. With a bit more work I've got it working with launch. Adding Log.d("thread", Thread.currentThread().name) shows that the delays are not blocking UI.

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val text = findViewById<TextView>(R.id.thing_text)
        val button = findViewById<Button>(R.id.thing_button)

        var count = 0

        button.setOnClickListener {
            launch {
                val job = async {
                    count++

                    val string = delayedStringOfInt(count)
                    updateTextView(text, string)
                }

                try {
                    job.await()
                } catch (e: IOException) {
                    makeToastFromException(e)
                }
            }
        }
    }

    fun makeToastFromException(e: Exception) {
        runOnUiThread {
            Toast.makeText(this@MainActivity, e.localizedMessage, Toast.LENGTH_SHORT).show()
        }
    }

    fun updateTextView(text: TextView, string: String) {
        runOnUiThread { text.text = string }
    }

    suspend fun delayedStringOfInt(int: Int): String {
        delay(2000)
        if (int % 4 == 0) throw IOException("Boom")
        return int.toString()
    }
}
TTransmit
  • 3,270
  • 2
  • 28
  • 43