Coroutines let Android code suspend without blocking a thread. The important design idea is structured concurrency: every task belongs to a scope whose lifetime, cancellation and failures are understood.
Practical guidance
- A suspend function may pause, but it does not automatically choose a background dispatcher.
- Use viewModelScope for work owned by a ViewModel and lifecycleScope for work owned by a LifecycleOwner.
- Move blocking I/O with withContext(Dispatchers.IO).
- Let cancellation propagate and rethrow CancellationException.
Working example
class UserRepository(private val api: UserApi) {
suspend fun user(id: Long): User = withContext(Dispatchers.IO) {
api.user(id).toDomain()
}
}
fun refresh(id: Long) = viewModelScope.launch {
runCatching { repository.user(id) }
.onSuccess { user -> _state.update { it.copy(user = user) } }
}
Common mistakes
- launch does not return a result; use async only for true concurrent composition.
- Catching Throwable can swallow cancellation.
- Creating unmanaged scopes leaks work beyond the screen that requested it.
Key takeaway
Design the owner and cancellation policy before choosing launch, async or a dispatcher. The scope is part of the feature architecture.
