Android Tutorial

Software Setup and Configuration

Android Studio

File Structure

Components

Core Topics

Layout

View

Button

Intent and Intent Filters

Toast

RecyclerView

Fragments

Adapters

Other UI Component

Image Loading Libraries

Date and Time

Material Design

Bars

Working with Google Maps

Chart

Animation

Database

Advance Android

Jetpack

Architecture

App Publish

App Monetization

Jobs, Waiting, Cancellation in Kotlin Coroutines

Kotlin Coroutines are a powerful way to handle asynchronous programming in Kotlin. They simplify tasks like threading and background work, making it more intuitive and readable. The primary purpose of coroutines is to handle long-running tasks that might otherwise block the main thread, such as network requests, database queries, etc.

Let's dive into Jobs, Waiting, and Cancellation in the context of Kotlin Coroutines:

1. Jobs:

A Job is a handle to the lifecycle of a coroutine, and it's the fundamental building block of Kotlin's structured concurrency model.

  • When you launch a coroutine using launch or async, it returns a Job object.
  • This Job can be used to manage the coroutine's lifecycle, i.e., to cancel it or wait for its completion.
val job = GlobalScope.launch { 
    // Coroutine code 
}

2. Waiting:

There are scenarios where you might want to wait for a coroutine to finish its execution. Kotlin provides several ways to achieve this:

  • join(): Waits for a coroutine represented by the Job to complete.

    val job = GlobalScope.launch {
        delay(1000L)
        println("World!")
    }
    println("Hello,")
    job.join()
    
  • await(): It's used with async coroutine builder. It waits for the result of the Deferred (a non-blocking future that represents a promise to provide a result later).

    val deferred = GlobalScope.async {
        delay(1000L)
        "World!"
    }
    println("Hello, ${deferred.await()}")
    

3. Cancellation:

One of the significant advantages of coroutines is the ease with which you can cancel running tasks:

  • cancel(): You can use the cancel() method on a Job object to cancel a coroutine. It's cooperative, meaning that the coroutine has to be checking for cancellation.

    val job = GlobalScope.launch {
        for (i in 1..10) {
            println("Count $i")
            delay(500L)
        }
    }
    
    delay(2000L)
    job.cancel()
    
  • Making computation code cancellable: By default, only suspending functions are cancellable. If you have a computation loop, you can periodically check for cancellation using isActive or by invoking yield().

    GlobalScope.launch {
        for (i in 1..10) {
            if (!isActive) break
            println("Count $i")
            // computation work...
        }
    }
    
  • Cancellation with a cause: You can cancel a coroutine with a specific exception using cancel(cause: Throwable).

  • Cancellation is contagious: If you cancel a parent coroutine, all its children (coroutines launched inside it) will also get cancelled.

  • CancellationException: When a coroutine is cancelled, it terminates with a CancellationException.

In Summary:

Coroutines provide a structured way to handle asynchronous operations. Through Job, you have fine-grained control over the lifecycle of your coroutines. You can efficiently wait for coroutines using methods like join() or await(), and cancelling running coroutines is made straightforward. Properly managing cancellation ensures resources are freed and avoids potential leaks in your app.

  1. Waiting for completion in Kotlin Coroutines example:

    You can use the runBlocking coroutine builder to wait for the completion of a coroutine. Here's an example:

    import kotlinx.coroutines.*
    
    fun main() = runBlocking {
        val job = launch {
            // Perform asynchronous or long-running task
            delay(1000)
            println("Coroutine completed")
        }
    
        println("Waiting for coroutine to complete")
        job.join() // Wait for the coroutine to complete
        println("Coroutine completed")
    }
    
  2. Cancellation and timeout in Kotlin Coroutines:

    Coroutines can be canceled using the cancel function. You can also use withTimeout to set a timeout for a coroutine. Here's an example:

    import kotlinx.coroutines.*
    
    fun main() = runBlocking {
        val job = launch {
            try {
                withTimeout(500) {
                    // Perform task with a timeout
                    delay(1000)
                }
            } catch (e: TimeoutCancellationException) {
                println("Task timed out")
            }
        }
    
        delay(1500)
        job.cancelAndJoin() // Cancel the job
    }
    
  3. Handling job cancellation in Kotlin Coroutines:

    Use the isActive property to check if a coroutine is still active. Handle cancellation using yield or ensureActive. Example:

    import kotlinx.coroutines.*
    
    fun main() = runBlocking {
        val job = launch {
            repeat(10) {
                if (!isActive) return@launch
                println("Working... $it")
                delay(100)
            }
        }
    
        delay(500)
        job.cancelAndJoin() // Cancel the job
    }
    
  4. Managing multiple coroutines with jobs in Kotlin:

    Use Job to manage multiple coroutines. You can wait for all jobs to complete using joinAll. Example:

    import kotlinx.coroutines.*
    
    fun main() = runBlocking {
        val job1 = launch {
            delay(1000)
            println("Job 1 completed")
        }
    
        val job2 = launch {
            delay(500)
            println("Job 2 completed")
        }
    
        joinAll(job1, job2) // Wait for both jobs to complete
        println("All jobs completed")
    }
    
  5. CoroutineScope and structured concurrency in Kotlin:

    Use CoroutineScope to create a scope for coroutines. Structured concurrency helps manage the lifecycle of coroutines. Example:

    import kotlinx.coroutines.*
    
    suspend fun performTask() {
        coroutineScope {
            val job1 = launch {
                delay(1000)
                println("Job 1 completed")
            }
    
            val job2 = launch {
                delay(500)
                println("Job 2 completed")
            }
    
            job1.join()
            job2.join()
            println("All jobs completed")
        }
    }
    
    fun main() = runBlocking {
        performTask()
    }
    
  6. Suspending functions and coroutine cancellation:

    Suspending functions can be canceled using coroutine cancellation. Example:

    import kotlinx.coroutines.*
    
    suspend fun performTask() {
        repeat(10) {
            yield() // Cooperatively yield to other coroutines
            println("Working... $it")
            delay(100)
        }
    }
    
    fun main() = runBlocking {
        val job = launch {
            performTask()
        }
    
        delay(500)
        job.cancelAndJoin()
    }
    
  7. Timeout and cancellation with async in Kotlin Coroutines:

    Use async for concurrent operations and await with a timeout. Example:

    import kotlinx.coroutines.*
    
    suspend fun performTask(): String {
        delay(1000)
        return "Task completed"
    }
    
    fun main() = runBlocking {
        val deferred = async {
            withTimeout(500) {
                performTask()
            }
        }
    
        try {
            val result = deferred.await()
            println(result)
        } catch (e: TimeoutCancellationException) {
            println("Task timed out")
        }
    }
    
  8. Kotlin Coroutines job lifecycle and states:

    Jobs have different states, such as Active, Completed, Cancelled, etc. Example:

    import kotlinx.coroutines.*
    
    fun main() = runBlocking {
        val job = launch {
            println("Job is active: ${job.isActive}")
            delay(500)
        }
    
        delay(100)
        println("Job is active: ${job.isActive}")
        job.join()
        println("Job is active: ${job.isActive}")
    }