1. What Exactly is a Coroutine?
A coroutine is frequently described as a lightweight thread. While an operating system thread has its own dedicated call stack and consumes roughly 1MB of memory, a coroutine is managed entirely in user space by the Kotlin runtime. You can launch 100,000 coroutines simultaneously on a modest mobile device without risking an OutOfMemoryError.
2. Dispatchers: Assigning Work to the Right Thread
Kotlin Coroutines provides three primary Dispatchers:
Dispatchers.Main: Bound to the Android UI thread. Use exclusively for UI manipulation, observing StateFlow, and lightweight operations.Dispatchers.IO: Optimized for blocking I/O tasks like network requests (Retrofit), database operations (Room), and file reading.Dispatchers.Default: Optimized for CPU-intensive tasks such as JSON parsing, sorting massive lists, or image matrix transformations.
3. StateFlow vs LiveData: Why StateFlow Won
For years, Google recommended LiveData as the primary state holder in ViewModels. However, LiveData had key limitations: it was closely coupled to the Android framework lifecycle and lacked rich transformation operators. StateFlow is pure Kotlin, making it portable to Kotlin Multiplatform (KMP), while offering thread safety and seamless integration with Compose.
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
sealed interface UiState {
object Loading : UiState
data class Success(val data: List) : UiState
data class Error(val message: String) : UiState
}
class ProductViewModel(private val repository: ProductRepository) : ViewModel() {
private val _uiState = MutableStateFlow(UiState.Loading)
val uiState: StateFlow = _uiState.asStateFlow()
init {
loadCatalog()
}
fun loadCatalog() {
viewModelScope.launch {
_uiState.value = UiState.Loading
try {
val products = repository.fetchProducts()
_uiState.value = UiState.Success(products)
} catch (e: Exception) {
_uiState.value = UiState.Error(e.localizedMessage ?: "Unknown network error")
}
}
}
}
4. Consuming StateFlow Safely in Jetpack Compose
In Compose, consume StateFlow using collectAsStateWithLifecycle() from the lifecycle-runtime-compose artifact. This ensures the Flow automatically cancels collection when the app enters the background, preventing battery drain:
@Composable
fun ProductScreen(viewModel: ProductViewModel) {
val state by viewModel.uiState.collectAsStateWithLifecycle()
when (val s = state) {
is UiState.Loading -> CircularProgressIndicator()
is UiState.Success -> ProductList(s.data)
is UiState.Error -> Text(text = "Error: ${s.message}", color = MaterialTheme.colorScheme.error)
}
}
Conclusion
By harnessing Kotlin Coroutines with Structured Concurrency and modeling UI states with StateFlow, you construct resilient, lifecycle-aware Android architectures that never block the UI thread.
Comments (0)
No comments yet. Share your thoughts below!
Leave a Comment
Share your thoughts or questions. Your email address remains private.