Kotlin Coroutines Basics - launch, async, withContext Guide
Kotlin coroutines simplify asynchronous programming. Master the core builders: launch for fire-and-forget, async for parallel results, and withContext for thread switching to write clean, non-blocking code.
launch - Fire and Forget
Use launch when you don't need a result:
fun main() = runBlocking {
launch {
delay(1000)
println("World!")
}
println("Hello")
}
// Output:
// Hello
// World! (after 1 second)
// With error handling
viewModelScope.launch {
try {
val data = loadData()
updateUI(data)
} catch (e: Exception) {
showError(e.message)
}
}
async/await - Parallel Results
Use async when you need results from multiple operations:
suspend fun loadUser(id: Int): User = delay(1000).run { User(id) }
suspend fun loadPosts(userId: Int): List<Post> = delay(500).run { emptyList() }
// Sequential (bad): 1.5 seconds
suspend fun loadUserDataSequential(id: Int) {
val user = loadUser(id) // 1 second
val posts = loadPosts(user.id) // 0.5 seconds
}
// Parallel (good): 1 second
suspend fun loadUserDataParallel(id: Int) = coroutineScope {
val userAsync = async { loadUser(id) }
val postsAsync = async { loadPosts(id) }
val user = userAsync.await()
val posts = postsAsync.await()
Pair(user, posts)
}
// With async builder
viewModelScope.launch {
try {
val (user, posts) = loadUserDataParallel(123)
updateUI(user, posts)
} catch (e: Exception) {
showError(e)
}
}
withContext - Thread Switching
Use withContext to switch coroutine context (thread):
// Switch to IO for database/network
suspend fun fetchFromDatabase(): List<User> = withContext(Dispatchers.IO) {
database.queryUsers() // Blocking call on IO thread
}
// Switch to Main for UI updates
viewModelScope.launch {
val data = withContext(Dispatchers.IO) {
loadData() // Long operation on IO thread
}
// Now back on Main dispatcher
updateUI(data) // UI update on main thread
}
// Custom dispatcher
suspend fun computeHeavy(): Int = withContext(Dispatchers.Default) {
(1..1000000).sum() // CPU-intensive on Default
}
Dispatchers - Thread Selection
Choose the right dispatcher for your task:
// Main: UI operations only
launch(Dispatchers.Main) {
textView.text = "Updated"
}
// IO: Network, database, file operations
launch(Dispatchers.IO) {
val data = api.fetchData()
database.insert(data)
}
// Default: CPU-intensive calculations
launch(Dispatchers.Default) {
val result = heavyCalculation()
}
// Unconfined: Inherit parent (rarely used)
launch(Dispatchers.Unconfined) {
// Context inherited from parent
}
// Custom: For specific use cases
val customDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
launch(customDispatcher) {
// Runs on custom thread
}
coroutineScope vs supervisorScope
// coroutineScope: One failure cancels all
suspend fun loadAllData() = coroutineScope {
val user = async { loadUser() } // Fails
val posts = async { loadPosts() } // Cancelled
val comments = async { loadComments() } // Cancelled
// Exception thrown
}
// supervisorScope: Each task independent
suspend fun loadAllDataIndependently() = supervisorScope {
val user = async { loadUser() } // Fails
val posts = async { loadPosts() } // Still runs
val comments = async { loadComments() } // Still runs
try {
user.await()
} catch (e: Exception) {
// Handle user error only
}
val postResult = try { posts.await() } catch (e: Exception) { emptyList() }
val commentResult = try { comments.await() } catch (e: Exception) { emptyList() }
}
Timeout and Delay
// Basic delay
delay(1000) // Suspend for 1 second
// With timeout
try {
withTimeout(5000) {
val data = loadData()
return data
}
} catch (e: TimeoutCancellationException) {
showError("Request timed out")
}
// Retry with timeout
suspend fun loadWithRetry(maxAttempts: Int = 3): String {
repeat(maxAttempts) { attempt ->
try {
return withTimeout(5000) { loadData() }
} catch (e: TimeoutCancellationException) {
if (attempt == maxAttempts - 1) throw e
delay(1000) // Wait before retry
}
}
error("Failed to load")
}
Core Patterns Summary
| Builder | Use Case | Returns | Exception Behavior |
|---|---|---|---|
| launch | Fire-and-forget | Job | Lost |
| async | Need result | Deferred | Must await |
| withContext | Thread switch | Result | Propagates |
| coroutineScope | Structured tasks | Result | Cancels all |
| supervisorScope | Independent tasks | Result | Independent handling |
8 Android app templates available on Gumroad
Top comments (0)