LukasLechnerDev / Kotlin-Coroutines-and-Flow-UseCases-on-Android

🎓 Learning Kotlin Coroutines and Flows for Android by example. 🚀 Sample implementations for real-world Android use cases. 🛠 Unit tests included!

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

usecase4/VariableAmountOfNetworkRequestsViewModel#performNetworkRequestsConcurrently crashes on exception

lgtout opened this issue · comments

Hi, Lukas!

I think there's a problem with the solution for

usecase4/VariableAmountOfNetworkRequestsViewModel#performNetworkRequestsConcurrently.

It crashes when I configure MockApi to return a 500 on the call to http://localhost/android-version-features/28.

Reading this about exceptions, it seems like try-catch will not catch the exception because async is not the direct child of the scope - launch is. So async will propagate the exception up to its parent (launch) and launch will throw the exception.

What do you think?

- Julian

Just did some more investigating. I see that the exception gets caught by the try-catch.
It is kotlinx.coroutines.JobCancellationException: Parent job is Cancelling; job=StandaloneCoroutine{Cancelling}@926b1ac
Then the app crashes.

I read your post about coroutine exceptions, which confirmed that the try-catch inside launch will catch calls to getAndroidVersions(), but will not catch calls to getAndroidVersionFeatures() because those take place within a non-top-level async.

Any exception thrown within the async is propagated to the root launch. And the only way to catch an exception propagated in this way is by installing a CoroutineExceptionHandler.

The following covers all exception cases:

fun performNetworkRequestsConcurrently() {
    uiState.value = UiState.Loading
    viewModelScope.launch(CoroutineExceptionHandler {
        _, throwable ->
        Timber.v(throwable)
        uiState.value = UiState.Error("Network Request failed")
    }) {
        try {
            val recentVersions = mockApi.getRecentAndroidVersions()
            val versionFeaturesJobs = recentVersions.map {
                androidVersion ->
                async {
                    mockApi.getAndroidVersionFeatures(
                            androidVersion.apiLevel
                    )
                }
            }
            val versionFeatures = versionFeaturesJobs.awaitAll()
            uiState.value = UiState.Success(versionFeatures)
        } catch (exception: Exception) {
            uiState.value = UiState.Error("Network Request failed")
        }
    }
}

Hello @LukasLechnerDev it seems that the issue described here is still present in the current version of the course

Using async directly rather than viewModelScope.async result in crash when the api response is in error