Structured concurrency means every coroutine has a defined lifetime, tied to a parent scope — no orphaned background work that outlives what launched it. You already used this with viewModelScope; here’s the underlying mechanics:
suspend fun loadDashboard() = coroutineScope {
val userDeferred = async { fetchUser() }
val postsDeferred = async { fetchPosts() }
// Both run concurrently; this suspends until BOTH complete
val user = userDeferred.await()
val posts = postsDeferred.await()
Dashboard(user, posts)
}
If fetchPosts() throws, coroutineScope automatically cancels the sibling userDeferred coroutine too — structured concurrency propagates failure and cancellation through the whole tree, preventing half-finished work from silently continuing.
Handling exceptions properly
viewModelScope.launch {
try {
val result = repository.riskyOperation()
_state.value = UiState.Success(result)
} catch (e: CancellationException) {
throw e // NEVER swallow cancellation — rethrow it always
} catch (e: Exception) {
_state.value = UiState.Error(e.message ?: "Unknown error")
}
}
Swallowing CancellationException is a classic, hard-to-debug mistake — it breaks structured concurrency’s ability to cancel a coroutine tree cleanly.
- Image
- SKU
- Rating
- Price
- Stock
- Availability
- Add to cart
- Description
- Content
- Weight
- Dimensions
- Additional information