Exception Handling in Kotlin Flow

Authors
  • Amit Shekhar
    Name
    Amit Shekhar
    Published on
Exception Handling in Kotlin Flow

I am Amit Shekhar, Co-Founder @ Outcome School, I have taught and mentored many developers, and their efforts landed them high-paying tech jobs, helped many tech companies in solving their unique problems, and created many open-source libraries being used by top companies. I am passionate about sharing knowledge through open-source, blogs, and videos.

Join Outcome School and get high paying tech job: Outcome School

Before we start, I would like to mention that, I have released a video playlist to help you crack the Android Interview: Check out Android Interview Questions and Answers.

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-cases in Android Development.

This blog is a part of the series I have written on Flow API in Kotlin:

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

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

A 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

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 in 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.IO)
    .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 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().catch { emitAll(flowOf(emptyList())) }
    .zip(getUsersWithError().catch { emitAll(flowOf(emptyList())) }) { usersFromApi, moreUsersFromApi ->
        val allUsersFromApi = mutableListOf<ApiUser>()
        allUsersFromApi.addAll(usersFromApi)
        allUsersFromApi.addAll(moreUsersFromApi)
        return@zip allUsersFromApi
    }
    .flowOn(Dispatchers.IO)
    .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.

Prepare yourself for Android Interview: Android Interview Questions

That's it for now.

Thanks

Amit Shekhar
Co-Founder @ Outcome School

You can connect with me on:

Follow Outcome School on:

Read all of our high-quality blogs here.