Sobes.tech
Middle

How to set up and use coroutines in Kotlin projects?

sobes.tech AI

Answer from AI

To set up coroutines, you need to add the appropriate dependency in the build.gradle file.

// build.gradle (app module)
dependencies {
    //... other dependencies
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") // For JVM, Android, JS, Native
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3") // For Android (if you need Dispatchers.Main)
}

Using coroutines involves:

  1. Scope: Defining the area in which coroutines will run and manage their lifecycle.

    • GlobalScope: The lifetime of the entire application, use cautiously as it can lead to memory leaks.
    • CoroutineScope: Created explicitly, allows control over cancellation.
    • Predefined scopes in frameworks (e.g., lifecycleScope in Android ViewModel).
    // Example with CoroutineScope
    val myScope = CoroutineScope(Dispatchers.Default + SupervisorJob()) // Creating a Scope with Dispatcher and Job
    
    fun performBackgroundTask() {
        myScope.launch { // Launching a coroutine
            // Long computation process
        }
    }
    
    fun cancelAllTasks() {
        myScope.cancel() // Cancel all coroutines in the Scope
    }
    
  2. Coroutine Builders: Functions to launch coroutines.

    • launch: Starts a coroutine that does not explicitly return a result (returns Job).
    • async: Starts a coroutine that returns a result as Deferred.
    import kotlinx.coroutines.*
    
    suspend fun fetchData() = coroutineScope { // coroutineScope - builder for structured concurrency
        val data1 = async { networkCall1() } // async returns Deferred
        val data2 = async { networkCall2() }
    
        // ... process data after retrieval
        data1.await() // Get result from Deferred, suspends coroutine until completion
        data2.await()
    }
    
    suspend fun networkCall1(): String {
        delay(1000) // Simulate network request
        return "Data 1"
    }
    
    suspend fun networkCall2(): String {
        delay(1500)
        return "Data 2"
    }
    
    fun startFetching() = runBlocking { // runBlocking - builder for blocking current thread (for main or tests)
        launch { // launch returns Job
            println("Fetching data...")
            fetchData()
            println("Data fetched!")
        }
    }
    
  3. Dispatchers: Define on which thread or thread pool the coroutine will run.

    • Dispatchers.Default: For CPU-intensive tasks.
    • Dispatchers.IO: For blocking I/O operations (file access, network).
    • Dispatchers.Main: Main thread (only for Android and Swing/JavaFX).
    • Dispatchers.Unconfined: Launches coroutine in the calling thread, suspends, then resumes in the thread that resumes execution.
    import kotlinx.coroutines.*
    
    fun simpleTask() {
        GlobalScope.launch(Dispatchers.IO) { // Specify dispatcher
            // Perform I/O operation
            println("Running on thread: ${Thread.currentThread().name}")
        }
    }
    
    fun main() = runBlocking {
        launch(Dispatchers.Default) {
            println("Default thread: ${Thread.currentThread().name}")
        }
        launch(Dispatchers.Unconfined) {
             println("Unconfined thread 1: ${Thread.currentThread().name}")
             delay(100) // Suspend
             println("Unconfined thread 2: ${Thread.currentThread().name}") // Resume in another thread (possibly)
        }
        delay(200) // Wait for coroutines to finish
    }
    
  4. Suspending functions: Functions marked with suspend keyword that can be suspended and resumed without blocking the thread. They can only be called from other suspending functions or within a coroutine block.

  5. Cancellation and exception handling: Coroutines support structured cancellation and exception propagation.

    • Cancellation:
      import kotlinx.coroutines.*
      
      fun cancellableTask() = GlobalScope.launch {
          try {
              repeat(1000) { i ->
                  println("Working $i ...")
                  delay(100) // suspend function that respects cancellation
              }
          } catch (e: CancellationException) {
              println("Task was cancelled!")
          } finally {
              println("Cleaning up...")
          }
      }
      
      fun main() = runBlocking {
          val job = cancellableTask()
          delay(500) // Wait a bit
          job.cancelAndJoin() // Cancel and wait for completion
      }
      
    • Exceptions: In case of launch, unhandled exceptions will crash the app (by default in Android). In case of async, exceptions are thrown when calling await(). For finer control, use CoroutineExceptionHandler or SupervisorJob.
    import kotlinx.coroutines.*
    
    fun main() = runBlocking {
        val handler = CoroutineExceptionHandler { _, exception ->
            println("Caught exception: $exception")
        }
    
        val job = GlobalScope.launch(handler) { // Apply handler
            throw IllegalStateException("Something went wrong")
        }
        job.join() // Wait for completion
    }
    
How to set up and use coroutines in Kotlin projects… - sobes.tech