SupervisorJob

Job vs SupervisorJob

Job():                         SupervisorJob():
  parent                         parent
   ├─ child A  ✗ fails             ├─ child A  ✗ fails
   ├─ child B  ✗ cancelled         ├─ child B  ✓ keeps running
   └─ parent   ✗ cancelled         └─ parent   ✓ still active

viewModelScope and lifecycleScope use a SupervisorJob, so one failed request does not stop other work on the screen.

Example

Example

import kotlinx.coroutines.*

fun main() = runBlocking {
    val log = CoroutineExceptionHandler { _, e -> println("  logged: ${e.message}") }

    println("With Job():")
    val normal = CoroutineScope(Job() + log)
    val a1 = normal.launch { delay(50); error("A failed") }
    val b1 = normal.launch { delay(100); println("  B finished") }
    joinAll(a1, b1)
    println("  scope active: ${normal.isActive}")

    println("With SupervisorJob():")
    val supervised = CoroutineScope(SupervisorJob() + log)
    val a2 = supervised.launch { delay(50); error("A failed") }
    val b2 = supervised.launch { delay(100); println("  B finished") }
    joinAll(a2, b2)
    println("  scope active: ${supervised.isActive}")
    supervised.cancel()
}

Output

With Job():
  logged: A failed
  scope active: false
With SupervisorJob():
  logged: A failed
  B finished
  scope active: true

Note: launch(SupervisorJob()) { ... } is a common mistake: it makes the new coroutine a child of a fresh SupervisorJob and breaks the link to the parent scope. Use supervisorScope { } or a scope created with SupervisorJob() instead.