|
| 1 | +--- |
| 2 | +layout: docs-fx |
| 3 | +title: kotlinx.coroutines |
| 4 | +permalink: /integrations/kotlinxcoroutines/ |
| 5 | +--- |
| 6 | + |
| 7 | +# Kotlin Coroutines and runtime support |
| 8 | + |
| 9 | +Kotlin offers a `suspend` system in the language, and it offers intrinsics in the standard library to build a library on top. These `intrinsic` functions allow you to `startCoroutine`s, `suspendCoroutine`s, build `CoroutineContext`s and so on. |
| 10 | + |
| 11 | +Kotlin's language suspension support can be found in the [kotlin.coroutines](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines/index.html) package. |
| 12 | + |
| 13 | +There are currently two libraries that provide a runtime for the language's suspension system. |
| 14 | + |
| 15 | +- [Arrow Fx](https://arrow-kt.io/docs/fx/) |
| 16 | +- [KotlinX Coroutines](https://github.com/Kotlin/kotlinx.coroutines) |
| 17 | + |
| 18 | +They can easily interop with each other, and Arrow Fx's integration module offers certain combinators to use Arrow Fx's with KotlinX structured concurrency in frameworks that have chosen to incorporate the KotlinX Coroutines library such as Android and Ktor. |
| 19 | + |
| 20 | +## Integrating Arrow Fx Coroutine with KotlinX Coroutine |
| 21 | + |
| 22 | +If you'd like to introduce Arrow Fx Coroutine in your project, you might want to keep using the KotlinX Coroutines style of cancellation with `CoroutineScope`. This is especially useful on *Android* projects where the Architecture Components [already provide handy scopes for you](https://developer.android.com/topic/libraries/architecture/coroutines#lifecycle-aware). |
| 23 | + |
| 24 | +### unsafeRunScoped & unsafeRunIO |
| 25 | + |
| 26 | +`scope.unsafeRunScoped(f, cb)` runs the specific Arrow Fx Coroutine program with a [CoroutineScope](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html), so it will be automatically cancelled when the scope does as well. |
| 27 | + |
| 28 | +Similarly, there's `f.unsafeRunIO(scope, cb)`, which works in the same way with different syntax: |
| 29 | + |
| 30 | +```kotlin:ank:playground |
| 31 | +import arrow.fx.coroutines.* |
| 32 | +import arrow.fx.coroutines.kotlinx.* |
| 33 | +import kotlinx.coroutines.CoroutineScope |
| 34 | +import kotlinx.coroutines.SupervisorJob |
| 35 | +
|
| 36 | +val scope = CoroutineScope(SupervisorJob()) |
| 37 | +
|
| 38 | +//sampleStart |
| 39 | +suspend fun sayHello(): Unit = |
| 40 | + println("Hello World") |
| 41 | +
|
| 42 | +suspend fun sayGoodBye(): Unit = |
| 43 | + println("Good bye World!") |
| 44 | +
|
| 45 | +suspend fun greet(): Unit { |
| 46 | + cancelBoundary() |
| 47 | + sayHello() |
| 48 | + cancelBoundary() |
| 49 | + sayGoodBye() |
| 50 | +} |
| 51 | +
|
| 52 | +fun main() { |
| 53 | + // This Arrow Fx Coroutine program would stop as soon as the scope is cancelled |
| 54 | + scope.unsafeRunScoped({ greet() }) { } |
| 55 | +
|
| 56 | + // alternatively, you could also do |
| 57 | + suspend { greet() }.unsafeRunIO(scope) { } |
| 58 | +} |
| 59 | +//sampleEnd |
| 60 | +``` |
| 61 | + |
| 62 | + |
| 63 | +## Alternatively, integrating Arrow Fx Coroutines with kotlinx.coroutines |
| 64 | + |
| 65 | +Sometimes you might not want to switch the runtime of your project, and slowly integrate to Arrow Fx Coroutines instead. For this use case, we've added some extensions to work with the KotlinX Coroutines runtime. |
| 66 | + |
| 67 | +*IMPORTANT NOTE*: The way kotlinx.coroutines handle errors is by throwing exceptions after you run your operations. Because of this, it's important to clarify that your operation might crash your app if you're not handling errors or try-catching the execution. |
| 68 | + |
| 69 | +### suspendCancellable |
| 70 | + |
| 71 | +The `suspendCancellable` function will turn an Arrow Fx Coroutine program into a KotlinX Coroutine, allowing you to cancel it within its scope like any other KotlinX Coroutine. |
| 72 | + |
| 73 | +```kotlin:ank:playground |
| 74 | +import arrow.fx.coroutines.* |
| 75 | +import arrow.fx.coroutines.kotlinx.* |
| 76 | +import kotlinx.coroutines.CoroutineScope |
| 77 | +import kotlinx.coroutines.launch |
| 78 | +import kotlinx.coroutines.SupervisorJob |
| 79 | +
|
| 80 | +val scope = CoroutineScope(SupervisorJob()) |
| 81 | +
|
| 82 | +//sampleStart |
| 83 | +suspend fun sayHello(): Unit = |
| 84 | + println("Hello World") |
| 85 | +
|
| 86 | +suspend fun sayGoodBye(): Unit = |
| 87 | + println("Good bye World!") |
| 88 | +
|
| 89 | +suspend fun greet(): Unit { |
| 90 | + cancelBoundary() |
| 91 | + sayHello() |
| 92 | + cancelBoundary() |
| 93 | + sayGoodBye() |
| 94 | +} |
| 95 | +
|
| 96 | +fun main() { |
| 97 | + // This Arrow Fx Coroutine program would stop as soon as the scope is cancelled |
| 98 | + scope.launch { |
| 99 | + suspendCancellable { greet() } |
| 100 | + } |
| 101 | +} |
| 102 | +//sampleEnd |
| 103 | +``` |
| 104 | + |
| 105 | +# Handling errors |
| 106 | + |
| 107 | +Let's briefly expand our previous example by adding a function that theoretically fetches (from network/db) the name of a person by their id: |
| 108 | + |
| 109 | +```kotlin:ank |
| 110 | +suspend fun fetchNameOrThrow(id: Int): String = |
| 111 | + "fetched name for $id" |
| 112 | +
|
| 113 | +suspend fun sayHello(): Unit = |
| 114 | + println("Hello ${fetchNameOrThrow(userId)}") |
| 115 | +
|
| 116 | +suspend fun sayGoodBye(): Unit = |
| 117 | + println("Good bye ${fetchNameOrThrow(userId)}!") |
| 118 | +``` |
| 119 | + |
| 120 | +Because we're using a suspend function, we know that this operation will either give us the name or throw an exception, which could cause our app to crash. |
| 121 | + |
| 122 | +But luckily, we're able to solve this for both combinators presented above using `Either.catch`: |
| 123 | + |
| 124 | +```kotlin:ank:playground |
| 125 | +import arrow.core.* |
| 126 | +import arrow.fx.coroutines.* |
| 127 | +import arrow.fx.coroutines.kotlinx.* |
| 128 | +import kotlinx.coroutines.CoroutineScope |
| 129 | +import kotlinx.coroutines.launch |
| 130 | +import kotlinx.coroutines.SupervisorJob |
| 131 | +
|
| 132 | +val scope = CoroutineScope(SupervisorJob()) |
| 133 | +
|
| 134 | +class NameNotFoundException(val id: Int): Exception("Name not found for id $id") |
| 135 | +val userId = 1 |
| 136 | +
|
| 137 | +//sampleStart |
| 138 | +suspend fun fetchNameOrThrow(id: Int): String = |
| 139 | + throw NameNotFoundException(id) |
| 140 | +
|
| 141 | +suspend fun sayHello(): Unit = |
| 142 | + println("Hello ${fetchNameOrThrow(userId)}") |
| 143 | +
|
| 144 | +suspend fun sayGoodBye(): Unit = |
| 145 | + println("Good bye ${fetchNameOrThrow(userId)}!") |
| 146 | +
|
| 147 | +fun greet(): Unit = Either.catch { |
| 148 | + cancelBoundary() |
| 149 | + sayHello() // This first call will throw and the exception be captured within this IO. |
| 150 | + cancelBoundary() |
| 151 | + sayGoodBye() // The second op will not be executed because of the above. |
| 152 | +}.getOrElse { println("Error printing greeting") } |
| 153 | +
|
| 154 | +fun main() { |
| 155 | +
|
| 156 | + // You can safely run greet() with unsafeRunScoped |
| 157 | + scope.unsafeRunScoped({ greet() }) { } |
| 158 | +
|
| 159 | + // or suspendCancellable + kotlinx. |
| 160 | + suspendCancellable { greet() } |
| 161 | +} |
| 162 | +//sampleEnd |
| 163 | +``` |
0 commit comments