Modern Android architecture demands asynchronous, non-blocking execution to keep the UI thread rendering smoothly at 120 FPS. In this guide, we dive into Kotlin Coroutines, dispatcher management, and structured concurrency to eliminate Application Not Responding (ANR) errors.
1. Choosing the Right Dispatcher in Kotlin Coroutines
Never run heavy disk operations or JSON deserialization on Dispatchers.Main. Understanding the boundary between CPU-bound and I/O-bound tasks is essential when architecting Kotlin Coroutines:
- Dispatchers.Main: Confined to the main UI thread. Ideal for UI updates and small computations.
- Dispatchers.IO: Backed by an elastic thread pool (up to 64 threads). Optimized for network API calls and SQLite database queries.
- Dispatchers.Default: Backed by a thread pool equal to the number of CPU cores. Optimized for intensive calculations and sorting large datasets.
class PerformanceRepository @Inject constructor(
private val apiService: ApiService,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) {
suspend fun fetchCachedMetrics(): Result<MetricsResponse> = withContext(ioDispatcher) {
runCatching {
apiService.getMetrics()
}
}
}
2. Always Leverage viewModelScope for Structured Concurrency
Memory leaks frequently happen when coroutines outlive their hosting Activity or Fragment. In modern Android development, tie your asynchronous work directly to the Lifecycle of the ViewModel using viewModelScope.
class MetricsViewModel @Inject constructor(
private val repository: PerformanceRepository
) : ViewModel() {
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
fun loadData() {
viewModelScope.launch {
repository.fetchCachedMetrics()
.onSuccess { data -> _uiState.value = UiState.Success(data) }
.onFailure { error -> _uiState.value = UiState.Error(error.message) }
}
}
}
3. Optimizing Jetpack Compose with StateFlow and Kotlin Coroutines
When collecting data flows in Jetpack Compose, use collectAsStateWithLifecycle() instead of standard collectAsState(). This guarantees coroutine collectors stop consuming system resources when the app goes into the background.
For more details on background threads, refer to the official Android Coroutines Documentation. You can also review our guide on core architecture principles.