DEV Community

myougaTheAxo
myougaTheAxo

Posted on

Kotlin Coroutines Basics - launch, async, withContext Guide

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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() }
}
Enter fullscreen mode Exit fullscreen mode

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")
}
Enter fullscreen mode Exit fullscreen mode

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)