Kotlin Coroutines simplify asynchronous programming on Android. Learn how to manage background work, parallel execution, and reactive data streams.
ViewModelScope & Dispatchers
Launch coroutines safely in your ViewModel:
class MyViewModel : ViewModel() {
fun loadData() {
viewModelScope.launch {
// Runs on Main dispatcher
val data = fetchData()
updateUI(data)
}
}
private suspend fun fetchData(): String {
return withContext(Dispatchers.IO) {
// Network/database work on IO thread
networkService.getData()
}
}
}
Dispatchers
Choose the right dispatcher for your task:
// Main - UI updates
viewModelScope.launch(Dispatchers.Main) {
uiState.value = newState
}
// IO - Network, database, file I/O
viewModelScope.launch(Dispatchers.IO) {
val result = database.query()
}
// Default - Heavy computation
viewModelScope.launch(Dispatchers.Default) {
val result = computeHeavyData()
}
Async/Await Parallel Execution
Run multiple coroutines in parallel:
viewModelScope.launch {
val userDeferred = async(Dispatchers.IO) { fetchUser() }
val postsDeferred = async(Dispatchers.IO) { fetchPosts() }
val user = userDeferred.await()
val posts = postsDeferred.await()
updateUI(user, posts)
}
Flow Basics
Emit multiple values over time:
fun getTemperatureUpdates(): Flow<Int> = flow {
while (currentCoroutineContext().isActive) {
val temperature = readTemperature()
emit(temperature)
delay(1000)
}
}
// Collect in UI
viewModelScope.launch {
getTemperatureUpdates().collect { temp ->
temperatureState.value = temp
}
}
StateFlow vs SharedFlow
Manage state and events:
// StateFlow - holds single state value
private val _uiState = MutableStateFlow(UiState.Loading)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
// SharedFlow - broadcasts events to multiple subscribers
private val _events = MutableSharedFlow<Event>()
val events: SharedFlow<Event> = _events.asSharedFlow()
suspend fun emitEvent(event: Event) {
_events.emit(event)
}
Flow Operators
Transform and filter data streams:
viewModelScope.launch {
searchQuery
.debounce(300.ms)
.distinctUntilChanged()
.flatMapLatest { query ->
searchDatabase(query)
}
.catch { error ->
_error.value = error.message
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = emptyList()
)
.collect { results ->
_searchResults.value = results
}
}
Compose Integration
Use Flow with Compose:
@Composable
fun MyScreen(viewModel: MyViewModel = viewModel()) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
when (uiState) {
is UiState.Loading -> LoadingScreen()
is UiState.Success -> SuccessScreen(uiState.data)
is UiState.Error -> ErrorScreen(uiState.message)
}
}
Error Handling with UiState
Manage loading, success, and error states:
sealed class UiState {
object Loading : UiState()
data class Success(val data: List<Item>) : UiState()
data class Error(val message: String) : UiState()
}
class MyViewModel : ViewModel() {
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
fun loadData() {
viewModelScope.launch {
try {
val data = fetchData()
_uiState.value = UiState.Success(data)
} catch (e: Exception) {
_uiState.value = UiState.Error(e.message ?: "Unknown error")
}
}
}
}
Mastering coroutines and Flow transforms you into an async programming expert.
8 Android app templates available: Gumroad
Top comments (0)