Free tutorials & notes in Hindi & English · Clean code examples 🧰 Free PDF & Image Tools →
💾 Data, Network & Architecture  ·  Lesson 32

Kotlin Coroutines in Android

Why coroutines?

Android draws the screen on the main thread. If you load a database or call an API there, the app freezes; after ~5 seconds Android shows "App isn't responding" (ANR). Coroutines let you write background work in normal top-to-bottom code — without callbacks.

Blocking main thread

  • Thread.sleep / network on main
  • UI frozen, no taps work
  • ANR dialog

Coroutine

  • suspend fun loadNotes()
  • thread is free while waiting
  • UI smooth, result applied later
Waiting without blocking

suspend functions and launch

These examples are plain Kotlin and the outputs are real. In an app you would use viewModelScope instead of runBlocking.

Kotlin
import kotlinx.coroutines.*

suspend fun loadNotes(): List<String> {
    delay(1000)                       // pretend database / network work
    return listOf("Maths homework", "Physics notes")
}

fun main() = runBlocking {
    println("1. Start (thread: main)")
    val job = launch {
        val notes = loadNotes()       // suspends, does NOT block the thread
        println("3. Loaded ${notes.size} notes")
    }
    println("2. UI is still free while loading…")
    job.join()
    println("4. Done")
}
1. Start (thread: main) 2. UI is still free while loading… 3. Loaded 2 notes 4. Done
Main threadCoroutinelaunch { }prints "2. UI is still free"delay(1000) – suspended, thread releasedresult ready → "3. Loaded 2 notes"
A suspended coroutine does not occupy a thread

Parallel work with async

Kotlin
import kotlinx.coroutines.*
import kotlin.system.measureTimeMillis

suspend fun getProfile(): String { delay(1000); return "Riya" }
suspend fun getMarks(): Int { delay(1000); return 92 }

fun main() = runBlocking {
    val sequential = measureTimeMillis {
        val p = getProfile(); val m = getMarks()
        println("Sequential: $p $m")
    }
    val parallel = measureTimeMillis {
        val p = async { getProfile() }
        val m = async { getMarks() }
        println("Parallel: ${p.await()} ${m.await()}")
    }
    println("sequential ≈ ${sequential / 1000}s, parallel ≈ ${parallel / 1000}s")
}
Sequential: Riya 92 Parallel: Riya 92 sequential ≈ 2s, parallel ≈ 1s

Dispatchers and withContext

DispatcherUse for
Dispatchers.MainUpdating views (default in viewModelScope / lifecycleScope)
Dispatchers.IOFiles, plain JDBC/SQLite, blocking network libraries
Dispatchers.DefaultCPU-heavy work: sorting big lists, parsing, image processing
Kotlin
import kotlinx.coroutines.*

fun main() = runBlocking {
    val result = withContext(Dispatchers.Default) {     // heavy CPU work off the main thread
        (1..1_000_000).sumOf { it.toLong() }
    }
    println("Sum = $result")

    try {
        withTimeout(500) { delay(2000); println("never printed") }
    } catch (e: TimeoutCancellationException) {
        println("Timed out after 500 ms")
    }

    val job = launch { repeat(10) { i -> println("tick $i"); delay(300) } }
    delay(700)
    job.cancel()                                      // like leaving the screen
    println("Cancelled: ${job.isCancelled}")
}
Sum = 500000500000 Timed out after 500 ms tick 0 tick 1 tick 2 Cancelled: true

Android scopes

Kotlin
// ViewModel – cancelled automatically when the ViewModel is cleared
class NoteViewModel(app: Application) : AndroidViewModel(app) {
    fun save(note: Note) = viewModelScope.launch {
        repo.save(note)                 // Room suspend function
    }
}

// Fragment / Activity – cancelled when the lifecycle is destroyed
viewLifecycleOwner.lifecycleScope.launch {
    val note = viewModel.getNote(id)    // suspend
    binding.etTitle.setText(note?.title)
}

// Collect a Flow only while the screen is visible
viewLifecycleOwner.lifecycleScope.launch {
    viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.results.collect { adapter.submitList(it) }
    }
}
viewModelScopelives as long as the ViewModel (survives rotation)lifecycleScope / viewLifecycleOwner.lifecycleScopelives as long as the Activity / fragment viewGlobalScopenever cancelled — avoid
Pick the scope that matches how long the work should live

Timeouts and cancellation

When a scope is cancelled (the user leaves the screen), all its coroutines stop at the next suspension point — no leaks, no updates to dead views. withTimeout stops slow work.

Handling errors

Kotlin – Student Diary NoteViewModel.loadTip()
viewModelScope.launch {
    _tip.value = TipState.Loading
    _tip.value = try {
        val tips = TipsApi.service.getTips().tips          // may throw IOException / HttpException
        TipState.Success(tips.random())
    } catch (e: Exception) {
        TipState.Error(e.message ?: "Network error")
    }
}

Real example from Student Diary

Kotlin
// AddEditNoteFragment – copy the picked photo on a background thread
private fun copyImage(uri: Uri) {
    viewLifecycleOwner.lifecycleScope.launch {
        val path = withContext(Dispatchers.IO) {                 // file work → IO thread
            ImageStore.copyToAppStorage(requireContext(), uri)
        }
        if (path == null) toast(R.string.image_error) else { imagePath = path; showImage() }  // back on Main
    }
}

// ReminderWorker – WorkManager's CoroutineWorker runs doWork() as a suspend function
override suspend fun doWork(): Result {
    val note = AppDatabase.get(applicationContext).noteDao().getById(noteId) ?: return Result.success()
    NotificationHelper.show(applicationContext, note.id, note.title, note.subject)
    return Result.success()
}

Common mistakes

  • Using GlobalScope.launch → work keeps running after the screen is gone.
  • Calling Thread.sleep inside a coroutine → blocks the thread; use delay.
  • Catching CancellationException and ignoring it → the coroutine cannot be cancelled. Re-throw it (or catch specific exceptions).
  • Updating a view from Dispatchers.IO → crash; switch back to Main (end the withContext block first).

Summary

  • suspend functions pause without blocking; start them with launch or async.
  • Main for UI, IO for files/blocking I/O, Default for CPU work; switch with withContext.
  • Use viewModelScope and lifecycleScope; wrap risky calls in try/catch.
✅ Practice

In a ViewModel, load "profile" and "marks" (two suspend functions with delay) in parallel with async, show a progress bar while loading, and show an error Snackbar if one of them throws.

Frequently Asked Questions

Do Room and Retrofit need withContext(Dispatchers.IO)?
No. Their suspend functions already switch to a background thread internally, so you can call them from viewModelScope on the main dispatcher.
What is the difference between launch and async?
launch starts work that returns no result (a Job). async returns a Deferred whose result you get with await(); use it to run things in parallel.
Is runBlocking used in Android apps?
No — it blocks the thread. It is only for main() functions and tests. In apps use viewModelScope or lifecycleScope.
← Android Tutorial (Kotlin)
🔗

Share this topic with a friend

यह topic किसी दोस्त को भेजें

Found it useful? Send it to a classmate learning the same thing.

अच्छा लगा? जो दोस्त यही सीख रहा है, उसे भेज दीजिए।

Next topicRetrofit: Call REST APIs Your progress: 32 / 43 (74%) · Lesson 33 / 43 Continue →

How was this lesson?

Your feedback helps us make the content better.

Write a review or suggestion (optional)

After approval, your name and city may appear publicly with your review. Please do not write your email or phone number.

▶ CodingEasily on YouTube

Learn with video lessons in easy Hindi

Featured video: RDBMS Part 3 – Class XI Commerce IT

▶ Subscribe – it’s free