diff --git a/formula-coroutines/src/main/java/com/instacart/formula/coroutines/SuspendAction.kt b/formula-coroutines/src/main/java/com/instacart/formula/coroutines/SuspendAction.kt new file mode 100644 index 00000000..5909c3d2 --- /dev/null +++ b/formula-coroutines/src/main/java/com/instacart/formula/coroutines/SuspendAction.kt @@ -0,0 +1,75 @@ +package com.instacart.formula.coroutines + +import com.instacart.formula.Action +import com.instacart.formula.Cancelable +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +/** + * Adapter which allows creating Formula [Action] from a Kotlin suspend function. Take a + * look at [SuspendAction.from] to create an [Action] from [suspend] type. + */ +interface SuspendAction : Action { + + companion object { + /** + * Creates an [Action] which will launch a [suspend] created by factory function [create] + * + * ``` + * SuspendAction.from { locationManager.currentLocation() }.onEvent { event -> + * transition() + * } + * ``` + */ + fun from( + create: suspend () -> Event + ): SuspendAction { + return SuspendActionImpl(null, create) + } + + /** + * Creates an [Action] which will launch a [suspend] created by factory function [create]. + * + * ``` + * SuspendAction.from(itemId) { repo.fetchItem(itemId) }.onEvent { event -> + * transition() + * } + * ``` + * + * @param key Used to distinguish this [Action] from other actions. + */ + fun from( + key: Any?, + create: suspend () -> Event + ): SuspendAction { + return SuspendActionImpl(key, create) + } + } + + suspend fun execute(): Event + + @OptIn(DelicateCoroutinesApi::class) + override fun start(send: (Event) -> Unit): Cancelable? { + val job = GlobalScope.launch(start = CoroutineStart.UNDISPATCHED) { + withContext(Dispatchers.Unconfined) { + val event = execute() + send(event) + } + } + return Cancelable(job::cancel) + } +} + +private data class SuspendActionImpl( + private val key: Any?, + private val factory: suspend () -> Event +) : SuspendAction { + + override suspend fun execute(): Event = factory() + + override fun key(): Any? = key +} \ No newline at end of file diff --git a/formula-coroutines/src/test/java/com/instacart/formula/coroutines/SuspendActionTest.kt b/formula-coroutines/src/test/java/com/instacart/formula/coroutines/SuspendActionTest.kt new file mode 100644 index 00000000..78e2a2fe --- /dev/null +++ b/formula-coroutines/src/test/java/com/instacart/formula/coroutines/SuspendActionTest.kt @@ -0,0 +1,20 @@ +package com.instacart.formula.coroutines + +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.delay +import org.junit.Test + +class SuspendActionTest { + + @Test + fun `default key is null`() { + val action = SuspendAction.from { delay(100) } + assertThat(action.key()).isNull() + } + + @Test + fun `specified key`() { + val action = SuspendAction.from("unique-key") { delay(100) } + assertThat(action.key()).isEqualTo("unique-key") + } +} \ No newline at end of file