withContext and main-safety

Coroutines Mid-level Kotlin 2.0+ API 24+ Reviewed By Best Latest Apps

Fill the blank

Make this repository call main-safe. The SDK call blocks.

class ReportRepository(private val io: CoroutineDispatcher, private val sdk: LegacySdk) {
    suspend fun report(): String = ____(io) {
        sdk.downloadBlocking()
    }
}
Options
Hint 1 of 1

Which builder returns the block's result?

Choose an answer, then open Show answer below to check it.

Show answer

Answer: A withContext

Correct. It switches to the IO dispatcher, waits, and returns the result to the caller.

Why not the others?
  • B launch. launch returns a Job, not the String, and isn't available outside a scope.
  • C async. async needs a scope and returns a Deferred, not the String.
  • D runBlocking. That blocks the caller's thread, which is the opposite of main-safe.

30-second answer

withContext runs a block on another context, usually another dispatcher, suspends the caller until the block finishes, and returns its result. A function is main-safe when it can be called from the main thread without freezing it, because it moves its own blocking or heavy work elsewhere with withContext.

Practise saying it · 35 s. No recording, just a timer

Explain withContext and main-safety.

Say your answer out loud, then check which of these points you covered:

  • withContext switches context and returns a result
  • It's sequential, not parallel
  • Main-safe functions pick their own dispatcher
  • NonCancellable for cleanup

Deep dive

The caller doesn't need to know the threading: viewModelScope.launch { val x = repo.load() } is fine on main if load is main-safe.

withContext is sequential. It does not start a parallel coroutine; use async for that.

Switching dispatchers has a small cost, so don't wrap every line; switch once around the heavy part.

withContext(NonCancellable) is the tool for cleanup that must finish after cancellation.

The SDK used above Compile-checked · Kotlin 2.4.21
interface LegacySdk {
    fun downloadBlocking(): String
}

Common mistakes

  • Using launch(Dispatchers.IO) and then posting results back by hand instead of withContext.
  • Making the caller responsible for switching threads.
  • Thinking withContext runs the block in parallel with the caller.

Senior take & follow-ups

Name the rule: the function that does the work owns the dispatcher. Then show one repository function using withContext for a blocking SDK.

Follow-up questions

Think of your answer first, then open the hint.

  1. Where does withContext(NonCancellable) belong?

    Show hint

    In a finally block, for cleanup that must run even after cancellation.

  2. Is a Retrofit suspend call main-safe?

    Show hint

    Yes, it does its own I/O off the main thread.

Suggest a fix if something here is wrong or out of date.