Coroutines in Android
Coroutines is modern way of asynchronous programming using Kotlin by offering a lightweight, efficient alternative to traditional multithreading tools
like Thread, Handler, or ThreadPoolExecutor. They require much less manual thread
management, suspending and resuming work without blocking underlying threads. They can enable thousands of concurrent operations with minimal overhead, while keeping
code readable and maintainable. They can be well integrated with Android lifecycles.
With Coroutines, there is no callback hell and thread synchronization complexities. Their integration with android lifecycles prevents memeory leaks and provide much better error handling. Suspend functions allow coroutines to pause executing on their thread without blocking during a network call and later either resume on same or different thread.
Key Features of Coroutines
- Lightweight Threading: Suspend and resume execution without blocking threads. Uses dispatchers (e.g.,
IO,Main) to switch threads efficiently. - Structured Concurrency: Run inside Android scopes like CoroutineScope,ViewModelScope, LifecycleScope to handle lifecycle automatically.
Coroutine Scopes in Android
- GlobalScope: Application-wide lifetime. Rarely used - can cause memory leaks if not properly managed.
- LifecycleScope: Bound to Activity/Fragment lifecycle - automatically cancels when destroyed.
- ViewModelScope: Tied to ViewModel's lifecycle - perfect for repository operations and data processing.
- CoroutineScope: For non-UI components (e.g., Repository, Domain Layer) - requires manual cancellation via
job.cancel().
Dispatchers
- Dispatchers.Main: UI operations on the main thread.
- Dispatchers.IO: Network and disk I/O operations.
- Dispatchers.Default: CPU-intensive operations.
- Dispatchers.Unconfined: Starts in the current thread, but can resume elsewhere.
Coroutine Builders
- launch: Launches a coroutine without returning a result.
- async: Returns a result through
Deferredandawait(). - runBlocking: Blocks the current thread (used for tests or console).
Suspend Functions
Suspend functions are declared with the suspend modifier and can only be called from within another coroutine or suspend function.
suspend fun fetchData(): String {
delay(1000)
return "Data from server"
}
---
Coroutines Launching and Synchronization
Kotlin Coroutines often started using launch and async, each serving different purposes.
Launch vs Async
launchis used when there is no need of a result. It runs a job that can be cancelled but does not return a value.asyncis used when there is need of a result. It returns aDeferredobject, and callawait()to get the result later.
While launch and async can be used independently, they can also be combined when there is need to perform multiple operations in parallel
and then synchronize them — for example, aggregating results before updating the UI.
Coordinating Multiple Coroutines
In many cases, there is need run multiple coroutines concurrently and wait for them to finish before proceeding, especially when updating the UI.
This is typically done using async along with awaitAll, or by collecting results manually.
// Inside a ViewModel or lifecycle-aware scope
viewModelScope.launch {
val deferred1 = async { fetchDataFromNetwork() }
val deferred2 = async { loadDataFromDb() }
val result1 = deferred1.await()
val result2 = deferred2.await()
updateUI(result1, result2)
}
---
In summary:
launchis for fire-and-forget tasks;asyncis for concurrent operations needing results.- Multiple coroutines can be synchronized using
await()orawaitAll(). CoroutineWorkerintegrates coroutines into WorkManager for long-running background tasks.
Using Coroutines with WorkManager
Run coroutines inside a custom Worker by using CoroutineWorker from WorkManager. This allows you to run suspend functions in the background,
even if the app is killed.
class MyCoroutineWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
val success = performBackgroundTask()
return if (success) Result.success() else Result.retry()
}
private suspend fun performBackgroundTask(): Boolean {
// Example: fetch from network
delay(1000)
return true
}
}
When enqueuing this worker, no need to manually manage threads or callbacks — coroutine context handles it efficiently.
Coroutine Error Handling
Coroutines provide multiple ways to handle exceptions, depending on where and how they are launched:
1. Try-Catch Inside Coroutine
lifecycleScope.launch {
try {
val result = fetchData()
println(result)
} catch (e: Exception) {
println("Error: ${e.message}")
}
}
2. Using CoroutineExceptionHandler
val handler = CoroutineExceptionHandler { _, exception ->
println("Caught exception: ${exception.message}")
}
lifecycleScope.launch(handler) {
throw RuntimeException("Test error")
}
Note: This works only for launch coroutines, not async. Exceptions from async must be caught via await().
3. Handling async Exceptions
val deferred = lifecycleScope.async {
throw IllegalArgumentException("Something went wrong")
}
lifecycleScope.launch {
try {
deferred.await()
} catch (e: Exception) {
println("Handled: ${e.message}")
}
}
4. SupervisorScope (isolate failure)
Use supervisorScope to ensure that failure of one child coroutine doesn’t cancel the others:
lifecycleScope.launch {
supervisorScope {
launch {
throw RuntimeException("Child failed")
}
launch {
delay(1000)
println("Other child continues")
}
}
}
---
Example: Coroutine in Android Activity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
lifecycleScope.launch {
try {
val result = fetchData()
// update UI
} catch (e: Exception) {
// show error to user
}
}
}
suspend fun fetchData(): String {
delay(1500)
return "Success"
}
}
---
Quiz Questions
- Are coroutines true threads managed by the OS?
- What happens if an exception occurs in a coroutine launched using
async? - How does
supervisorScopediffer from a regular coroutine scope? - What happens if you forget to handle exceptions in a coroutine?
For full coroutine usage in Android, see this example project: Coroutine Fetch JSON (GitHub)