Kotlin Coroutines, Flow, and Async Patterns¶
Kotlin coroutines for async programming: suspend functions, dispatchers, scopes, structured concurrency, error handling, Flow for reactive streams, StateFlow, and Android integration.
Key Facts¶
- Coroutines are lightweight (few hundred bytes vs ~1MB per thread) - millions possible
suspendfunctions can be paused/resumed without blocking the threadviewModelScopeauto-cancels when ViewModel is cleared;lifecycleScopefollows Activity/Fragment lifecycleDispatchers.IOfor network/DB,Dispatchers.Mainfor UI,Dispatchers.Defaultfor CPU work- Structured concurrency: child coroutines inherit parent scope; parent cancellation cancels all children
SupervisorJob: one child failure does NOT cancel siblingsFlow= cold async stream;StateFlow= hot stream with current value (LiveData replacement)
Patterns¶
Suspend Functions and Builders¶
suspend fun fetchUser(id: Int): User {
val response = api.getUser(id) // suspends, doesn't block
return response.body() ?: throw Exception("Not found")
}
// launch - fire and forget (returns Job)
viewModelScope.launch {
val users = repository.getUsers()
_users.value = users
}
// async - returns Deferred (await for result)
val deferred = viewModelScope.async { repository.getUsers() }
val users = deferred.await()
Dispatchers¶
| Dispatcher | Use | Pool |
|---|---|---|
Dispatchers.Main | UI updates | Main thread |
Dispatchers.IO | Network, DB, file I/O | 64+ threads |
Dispatchers.Default | CPU-intensive (sorting, parsing) | CPU core count |
viewModelScope.launch(Dispatchers.IO) {
val data = repository.fetchFromNetwork()
withContext(Dispatchers.Main) { textView.text = data }
}
Scopes¶
// ViewModel scope - cancelled when VM cleared
viewModelScope.launch { ... }
// Lifecycle scope - tied to Activity/Fragment
lifecycleScope.launch { ... }
lifecycleScope.launchWhenStarted { ... } // pauses when stopped
// Custom scope
val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
scope.launch { ... }
scope.cancel() // must cancel manually
Structured Concurrency (Parallel)¶
viewModelScope.launch {
val users = async { api.getUsers() }
val orders = async { api.getOrders() }
// Both run in parallel, await both
_data.value = CombinedData(users.await(), orders.await())
}
Error Handling¶
viewModelScope.launch {
try {
val result = repository.getData()
_data.value = result
} catch (e: HttpException) { _error.value = "Server: ${e.code()}" }
catch (e: IOException) { _error.value = "Network error" }
}
// Global handler
val handler = CoroutineExceptionHandler { _, e -> _error.postValue(e.message) }
viewModelScope.launch(handler) { ... }
Flow (Cold Reactive Stream)¶
fun getUsers(): Flow<List<User>> = flow {
while (true) {
emit(api.getUsers())
delay(30_000) // refresh every 30s
}
}
// Collecting
viewModelScope.launch {
repository.getUsers()
.catch { e -> _error.value = e.message }
.collect { users -> _users.value = users }
}
StateFlow (Hot Stream, LiveData Replacement)¶
class MyViewModel : ViewModel() {
private val _uiState = MutableStateFlow(UiState.Loading)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
fun loadData() {
viewModelScope.launch {
_uiState.value = UiState.Loading
try { _uiState.value = UiState.Success(repository.getData()) }
catch (e: Exception) { _uiState.value = UiState.Error(e.message) }
}
}
}
sealed class UiState {
object Loading : UiState()
data class Success(val data: List<User>) : UiState()
data class Error(val message: String?) : UiState()
}
Flow Operators¶
flow
.filter { it.isActive }
.map { it.toUiModel() }
.debounce(300)
.distinctUntilChanged()
.flatMapLatest { query -> searchApi(query) }
.onStart { showLoading() }
.onCompletion { hideLoading() }
.catch { e -> showError(e) }
.collect { result -> showResult(result) }
Gotchas¶
runBlockingblocks the current thread - NEVER use on Android main thread (only for tests/main)GlobalScope.launchhas no lifecycle awareness - coroutines leak if not cancelled manuallySupervisorJobonly works if it's the parent of child coroutines - not if passed tolaunchStateFlowrequires initial value;SharedFlowdoes notcollectis a terminal suspending operation - code aftercollectwon't run until flow completes
See Also¶
- java concurrency - Java threading for comparison
- android architecture mvvm - Coroutines in ViewModel
- android networking retrofit - Retrofit suspend functions