Custom Scopes and GlobalScope

Why Not GlobalScope

GlobalScope.launch creates a coroutine with no parent. Nobody cancels it, nobody waits for it, and its errors are not reported to a caller. If the screen that started it is closed, it keeps running and may leak memory. The compiler marks it as a delicate API.

// Bad: leaks if the screen closes
GlobalScope.launch { repository.sync() }

// Good: tied to a lifecycle
viewModelScope.launch { repository.sync() }

Your Own Scope

Example

Classes that live longer than one function (a repository, a download manager) create their own scope and cancel it in close().

import kotlinx.coroutines.*

class DownloadManager : AutoCloseable {
    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default + CoroutineName("downloads"))
    private val jobs = mutableListOf<Job>()

    fun download(file: String, ms: Long) {
        jobs += scope.launch {
            try {
                delay(ms)
                println("Downloaded $file")
            } finally {
                if (!isActive) println("Stopped $file")
            }
        }
    }

    override fun close() = scope.cancel()
    suspend fun awaitJobs() = jobs.joinAll()
}

fun main() = runBlocking {
    val manager = DownloadManager()
    manager.download("song.mp3", 100)
    delay(300)
    manager.download("movie.mp4", 5000)
    delay(100)
    manager.close()        // cancels the movie download
    manager.awaitJobs()
    println("Manager closed")
}

Output

Downloaded song.mp3
Stopped movie.mp4
Manager closed

Note: Use SupervisorJob() in long-lived scopes so one failed task does not kill the whole scope.