0
votes

I found some documentation arguing about exception handling in Kotlin's coroutines with launch and async. But I could not found the solution dealing with the withContext.

suppose I have a coroutine like:

fun bar(path: String) {
    viewModelScope.launch {
        val foo = withContext(Dispatchers.IO) {
             foo(path)
        }
    }
}

fun foo(path: String) {
    // do something...
    val media = MediaMetadataRetriever()
    media.setDataSource(path)  // may throw IllegalArgumentException according to API's doc
    return media.frameAtTime
}

viewModelScope is imported from the lifecycle-viewmodel-ktx's implementation using a SupervisorJob.

Where should I put a try-catch block to deal with the IOException here?

1
From the design lead of Kotlin: medium.com/@elizarov/kotlin-and-exceptions-8062f589d07 TLDR: Don't throw exceptions unless they're runtime exceptions (caused by programmer error). Use a result wrapper or return null for errors. Wrap exceptions from Java libraries in one of the above using your own helper function when the standard lib doesn't provide it. try-catch in Kotlin is a code smell, unless it is in one of these helper functions. - Tenfour04
@Tenfour04 I agree with you. But my problem is that the foo function here uses an API as it throws exception according to documentation. I edited my code here. - Jian Guo
withContext should return a value. You should catch exception around your API call and make foo() return failure/success object. Otherwise exception will be propagated as withContext result and you can catch it there. If you don't it's gonna be rethrown again and default exception handler of launch will throw it as uncaught exception & your app will crash. - Pawel

1 Answers

3
votes

try-catch in Kotlin coroutines feels a little clumsy, but I think it's partially because the designers of Kotlin don't think you should be using it in the first place. When something is a checked exception (caused by something outside the programmer's control), their recommendation is to wrap it or return null. For example:

fun foo(path: String): Bitmap? {
    // do something...
    val media = MediaMetadataRetriever()
    try {
        media.setDataSource(path)
    catch (e: IOException) {
        Log.e(TAG, "Failed to set media data source", e)
        return null
    }
    return media.frameAtTime
}

fun bar(path: String) {
    viewModelScope.launch {
        val foo = withContext(Dispatchers.IO) {
             foo(path)
        }
        if (foo == null) {
            // show error to user or something
        } else {
           // do something with smart-cast non-null foo Bitmap
        }
    }
}

By the way, for a blocking function like foo that you will never call from the main thread, I suggest making it a suspend function and moving the withContext(Dispatchers.IO) into the function so it is self-contained and you can freely call it from any coroutine.