Introduction
Modern Android applications perform many operations that should not block the main thread. Network requests, database operations, file processing, and background calculations require efficient asynchronous programming.
Kotlin Coroutines provide a lightweight solution for asynchronous programming by allowing developers to write asynchronous code in a sequential and readable way.
Kotlin Flow extends coroutines by providing reactive streams that emit multiple values over time. Together, Coroutines and Flow are the foundation of modern Android architectures using ViewModel, Room, Retrofit, and Jetpack Compose.

What Are Kotlin Coroutines?
Kotlin Coroutines are a concurrency framework that allows developers to execute long-running operations without blocking the main thread.
Unlike traditional threads, coroutines are lightweight and can run thousands of concurrent operations with minimal overhead.
| Threads | Coroutines |
|---|---|
| Heavyweight | Lightweight |
| More memory usage | Minimal memory usage |
| Blocking operations | Suspending operations |
| Harder cancellation | Structured cancellation |
Why Use Coroutines in Android?
- ●
Avoid blocking the main UI thread
- ●
Simplify asynchronous code
- ●
Handle API requests easily
- ●
Support cancellation
- ●
Work naturally with ViewModel
- ●
Integrate with Jetpack libraries
Suspend Functions
A suspend function is a function that can pause execution without blocking the underlying thread.
When the suspended operation completes, the coroutine continues from where it stopped.
class UserRepository {
suspend fun getUsers(): List<User> {
return api.getUsers()
}
}
Coroutine Scope
A CoroutineScope defines the lifetime of coroutines. When the scope is cancelled, all child coroutines are automatically cancelled.
- ●
viewModelScope
- ●
lifecycleScope
- ●
rememberCoroutineScope
- ●
CoroutineScope
viewModelScope
viewModelScope is the recommended scope for running coroutines inside Android ViewModels.
class UserViewModel(
private val repository: UserRepository
) : ViewModel() {
fun loadUsers() {
viewModelScope.launch {
val users =
repository.getUsers()
}
}
}
Coroutine Dispatchers
Dispatchers define which thread or thread pool executes coroutine work.
| Dispatcher | Purpose |
|---|---|
| Main | UI operations |
| IO | Network and database operations |
| Default | CPU intensive work |
| Unconfined | Advanced use cases |
Using Dispatchers Correctly
class UserRepository {
suspend fun loadUsers()
: List<User> {
return withContext(
Dispatchers.IO
) {
api.getUsers()
}
}
}
Structured Concurrency
Structured concurrency ensures that coroutines follow a predictable lifecycle. Child coroutines belong to a parent scope and are cancelled together.
- ●
Automatic cancellation
- ●
Error propagation
- ●
Lifecycle awareness
- ●
Resource management
Coroutine Builders: launch and async
Coroutine builders create and start coroutines. The two most commonly used builders are launch and async.
| Builder | Returns | Usage |
|---|---|---|
| launch | Job | Fire and forget operations |
| async | Deferred | Operations returning values |
Using launch
launch starts a coroutine that does not return a result. It is commonly used for updating state, saving data, or triggering background operations.
viewModelScope.launch {
repository.saveUser(
user
)
}
Using async and await
async is used when multiple operations should run concurrently and produce results.
val users = async {
userRepository.getUsers()
}
val settings = async {
settingsRepository.getSettings()
}
val result = combine(
users.await(),
settings.await()
)
Exception Handling in Coroutines
Coroutine errors should be handled explicitly to prevent application crashes and provide better user experiences.
viewModelScope.launch {
try {
repository.loadData()
}
catch(exception: Exception) {
println(
exception.message
)
}
}
CoroutineExceptionHandler
CoroutineExceptionHandler allows centralized handling of uncaught coroutine exceptions.
val handler =
CoroutineExceptionHandler {
_, exception ->
println(
exception.message
)
}
What Is Kotlin Flow?
Flow is a Kotlin API for creating asynchronous streams of data. Unlike suspend functions that return a single value, Flow can emit multiple values over time.
Flow is commonly used for observing database changes, UI state updates, network streams, and reactive application behavior.
| Suspend Function | Flow |
|---|---|
| Returns one value | Returns multiple values |
| Single operation | Continuous stream |
| Request-response | Reactive updates |
Creating a Flow
A Flow can be created using the flow builder.
fun numbersFlow() =
flow {
emit(1)
emit(2)
emit(3)
}
Collecting Flow Values
Flows are cold streams. They do not start emitting values until they are collected.
numbersFlow()
.collect { value ->
println(value)
}
Flow Operators
Flow provides many operators for transforming, filtering, and combining data streams.
- ●
map
- ●
filter
- ●
combine
- ●
flatMapLatest
- ●
debounce
- ●
distinctUntilChanged
Using map Operator
usersFlow
.map { users ->
users.size
}
.collect { count ->
println(count)
}
Using filter Operator
usersFlow
.filter { user ->
user.active
}
.collect {
println(it)
}
StateFlow in Android
StateFlow is a special type of Flow designed for representing state. It always contains the latest value and immediately provides it to new collectors.
StateFlow is the recommended replacement for LiveData in many modern Android applications.
class UserViewModel
: ViewModel() {
private val _state =
MutableStateFlow(
emptyList<User>()
)
val state =
_state.asStateFlow()
}
StateFlow with Jetpack Compose
Compose integrates naturally with StateFlow because UI automatically updates when the state changes.
@Composable
fun UserScreen(
viewModel: UserViewModel
) {
val users by viewModel.state
.collectAsState()
UserList(users)
}
SharedFlow in Android
SharedFlow is designed for broadcasting events to multiple collectors. It is commonly used for one-time UI events.
- ●
Navigation events
- ●
Toast messages
- ●
Snackbar notifications
- ●
Analytics events
SharedFlow Example
private val _events =
MutableSharedFlow<String>()
val events =
_events.asSharedFlow()
fun showMessage() {
viewModelScope.launch {
_events.emit(
"Saved successfully"
)
}
}
Room Database with Flow
Room integrates directly with Flow and can automatically emit updates whenever database content changes.
@Query(
"SELECT * FROM users"
)
fun observeUsers()
: Flow<List<User>>
Retrofit with Coroutines
Retrofit supports suspend functions for modern API communication without callback-based code.
interface ApiService {
@GET("users")
suspend fun getUsers()
: List<User>
}
Flow Cancellation
One of the biggest advantages of Kotlin Flow is automatic cancellation support. When the collecting coroutine is cancelled, the Flow operation stops automatically.
viewModelScope.launch {
userFlow.collect {
updateUi(it)
}
}
When the ViewModel is destroyed, viewModelScope is cancelled and the Flow collector stops running.
Combining Multiple Flows
Real applications often need data from multiple sources. Flow provides operators that allow combining multiple streams together.
val userFlow =
userRepository.user
val settingsFlow =
settingsRepository.settings
combine(
userFlow,
settingsFlow
) { user, settings ->
UserScreenState(
user,
settings
)
}
flatMapLatest Operator
flatMapLatest is commonly used for search functionality because it automatically cancels previous requests when a new value arrives.
searchQuery
.flatMapLatest { query ->
repository.search(query)
}
.collect {
updateResults(it)
}
Debounce for Search Optimization
The debounce operator waits until the user stops typing before executing an operation. It prevents unnecessary API requests.
searchText
.debounce(500)
.collect { query ->
searchUsers(query)
}
Repository Pattern with Coroutines and Flow
A common Android architecture approach is to expose Flow from repositories and allow ViewModels to transform data into UI state.
class UserRepository(
private val api: ApiService,
private val database: UserDao
) {
fun users():
Flow<List<User>> {
return database
.observeUsers()
}
}
Creating UI State with Coroutines
A production Android application should represent UI states explicitly instead of manually controlling individual UI components.
sealed interface UiState {
data object Loading
: UiState
data class Success(
val users: List<User>
) : UiState
data class Error(
val message: String
) : UiState
}
Testing Kotlin Coroutines
Coroutine-based code should be tested using Kotlin coroutine testing tools. Tests should control coroutine execution instead of waiting for real delays.
- ●
runTest
- ●
TestDispatcher
- ●
TestScope
- ●
Virtual time
Coroutine Test Example
@Test
fun loadUsersTest() = runTest {
viewModel.loadUsers()
assertEquals(
5,
viewModel.state.value.size
)
}
Best Practices for Coroutines
- ●
Use suspend functions for one-time operations
- ●
Use Flow for continuous updates
- ●
Avoid GlobalScope
- ●
Use lifecycle-aware scopes
- ●
Handle exceptions properly
- ●
Keep business logic outside UI
💡 Android Recommendation
Prefer viewModelScope for ViewModel operations because it automatically cancels work when the ViewModel is destroyed.
Common Coroutine Mistakes
- ●
Running network calls on the Main dispatcher
- ●
Ignoring cancellation
- ●
Using blocking code inside coroutines
- ●
Collecting Flow incorrectly
- ●
Creating too many collectors
- ●
Using GlobalScope
Coroutines vs Traditional Callbacks
| Callbacks | Coroutines |
|---|---|
| Nested callback code | Sequential readable code |
| Manual error handling | Structured exceptions |
| Hard cancellation | Automatic cancellation |
| More boilerplate | Cleaner syntax |
Modern Android Architecture with Coroutines and Flow
The recommended architecture for modern Android applications combines Jetpack Compose, ViewModel, Coroutines, Flow, repositories, and clean separation of responsibilities.

- ●
Compose collects UI state
- ●
ViewModel manages state
- ●
Use cases contain business logic
- ●
Repositories provide data
- ●
Flow streams updates
Frequently Asked Questions
Are Kotlin Coroutines replacing threads?
Should I use LiveData or StateFlow?
When should I use Flow instead of suspend functions?
Can Room and Retrofit work with Flow?
Are Coroutines only for Android?
Conclusion
Kotlin Coroutines and Flow are essential technologies for modern Android development. They provide a clean and efficient approach for asynchronous programming, reactive data handling, and scalable application architecture. Combined with Jetpack Compose, ViewModel, and Clean Architecture, they enable developers to build responsive and maintainable Android applications.
