Exception Handling in Kotlin Flow

In this blog, we are going to learn about the Exception Handling in Kotlin Flow. We will also learn how to handle errors in the real-use of Android Development.

Before starting, for your information, this blog post is a part of the series that we are writing on Flow APIs in Kotlin Coroutines.

Resources to get started with Kotlin Flow:

Let's get started.

In Flow, the collection can complete with or without an exception. However, if there is an exception, we can catch that using the catch operator.

For example

Without using the catch operator: It will raise the exception and crash.

(1..5).asFlow()
.map {
    // raise exception when the value is 3, intentionally done for the sake of example
    check(it != 3) { "Value $it" } // raise exception
    it * it
}
.onCompletion {
    Log.d(TAG, "onCompletion")
}
.collect {
    Log.d(TAG, it.toString())
}

The result will be:

1

4

onCompletion

Exception in thread "main" java.lang.IllegalStateException: Value 3

Crash will happen!

Now, using the catch operator: It will give the handle to us.

(1..5).asFlow()
.map {
    // raise exception when the value is 3, intentionally done for the sake of example
    check(it != 3) { "Value $it" } // raise exception
    it * it
}
.onCompletion {
    Log.d(TAG, "onCompletion")
}
.catch { e ->
    Log.d(TAG, "Caught $e")
}
.collect {
    Log.d(TAG, it.toString())
}

The result will be:

1

4

onCompletion

Caught java.lang.IllegalStateException: Value 3

No crash! Only log.

Note: Whether we use the catch operator or not, in both cases, the emission will stop and it will go in the "onCompletion".

Now, let's take a real use-case of Android Development.

I will be using this project for the implementation part. If you have not gone through the project, you should go through and then come back. The project follows a basic MVVM Architecture for simplicity. You can find the complete code for the implementation mentioned in this blog in the project itself.

Assume that you have two network calls like below:

  • getUsers()
  • getMoreUsers()

And we are making both the network calls in parallel using the zip operator of Flow.

getUsers()
    .zip(getMoreUsers()) { usersFromApi, moreUsersFromApi ->
        val allUsersFromApi = mutableListOf<ApiUser>()
        allUsersFromApi.addAll(usersFromApi)
        allUsersFromApi.addAll(moreUsersFromApi)
        return@zip allUsersFromApi
    }
    .flowOn(Dispatchers.Default)
    .catch { e ->
        // handle error
    }
    .collect {
        // handle response
    }

In this case, if any of the network calls fail, it will give the handle to us inside the catch. And we can handle that based on our use-cases.

Suppose, we have another use-case in which if one of the network calls fail, we want to continue with the data from another network call.

We have an another network call to stimulate the error:

  • getUsersWithError()

In this case, we can return the empty list for the network in which we have received the error using the emitAll by catching the individual error like below:

getUsers()
    .zip(
        getUsersWithError()
            .catch { emitAll(flowOf(emptyList())) }) { usersFromApi, moreUsersFromApi ->
        val allUsersFromApi = mutableListOf<ApiUser>()
        allUsersFromApi.addAll(usersFromApi)
        allUsersFromApi.addAll(moreUsersFromApi)
        return@zip allUsersFromApi
    }
    .flowOn(Dispatchers.Default)
    .catch { e ->
        // handle error
    }
    .collect {
        // handle response
    }

Note: We can apply catch to the individual flow and emit data in case of any error.

This was all about the exception handling in Kotlin Flow.

That's it for now.

Also, Let’s become friends on Twitter, Linkedin, Github, Quora, and Facebook.