From 64298aee1d7bfdda2e068a6f73b6c97d7d20d9e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petrus=20Nguy=E1=BB=85n=20Th=C3=A1i=20H=E1=BB=8Dc?= Date: Fri, 20 May 2022 16:58:14 +0700 Subject: [PATCH 1/3] feat: add defer and flowFromSuspend --- .idea/misc.xml | 2 +- api/FlowExt.api | 8 +++ .../kotlin/com/hoc081098/flowext/defer.kt | 31 ++++++++ .../com/hoc081098/flowext/flowFromSuspend.kt | 40 +++++++++++ .../kotlin/com/hoc081098/flowext/DeferTest.kt | 70 +++++++++++++++++++ .../hoc081098/flowext/FlowFromSuspendTest.kt | 59 ++++++++++++++++ 6 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 src/commonMain/kotlin/com/hoc081098/flowext/defer.kt create mode 100644 src/commonMain/kotlin/com/hoc081098/flowext/flowFromSuspend.kt create mode 100644 src/commonTest/kotlin/com/hoc081098/flowext/DeferTest.kt create mode 100644 src/commonTest/kotlin/com/hoc081098/flowext/FlowFromSuspendTest.kt diff --git a/.idea/misc.xml b/.idea/misc.xml index 46eeceb6..79c1af21 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,7 +1,7 @@ - + diff --git a/api/FlowExt.api b/api/FlowExt.api index 0051200f..4cdcb354 100644 --- a/api/FlowExt.api +++ b/api/FlowExt.api @@ -30,6 +30,10 @@ public final class com/hoc081098/flowext/ConcatKt { public static final fun startWith (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow; } +public final class com/hoc081098/flowext/DeferKt { + public static final fun defer (Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow; +} + public abstract interface class com/hoc081098/flowext/DelayStrategy { public abstract fun nextDelay-3nIYWDw (Ljava/lang/Throwable;J)J } @@ -97,6 +101,10 @@ public final class com/hoc081098/flowext/FlatMapFirstKt { public static final fun flattenFirst (Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow; } +public final class com/hoc081098/flowext/FlowFromSuspendKt { + public static final fun flowFromSuspend (Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow; +} + public final class com/hoc081098/flowext/IntervalKt { public static final fun interval (JJ)Lkotlinx/coroutines/flow/Flow; public static final fun interval-QTBD994 (JJ)Lkotlinx/coroutines/flow/Flow; diff --git a/src/commonMain/kotlin/com/hoc081098/flowext/defer.kt b/src/commonMain/kotlin/com/hoc081098/flowext/defer.kt new file mode 100644 index 00000000..0071124b --- /dev/null +++ b/src/commonMain/kotlin/com/hoc081098/flowext/defer.kt @@ -0,0 +1,31 @@ +/* + * MIT License + * + * Copyright (c) 2021-2022 Petrus Nguyễn Thái Học + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.hoc081098.flowext + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.flow + +public fun defer(flowFactory: suspend () -> Flow): Flow = flow { emitAll(flowFactory()) } diff --git a/src/commonMain/kotlin/com/hoc081098/flowext/flowFromSuspend.kt b/src/commonMain/kotlin/com/hoc081098/flowext/flowFromSuspend.kt new file mode 100644 index 00000000..175575f3 --- /dev/null +++ b/src/commonMain/kotlin/com/hoc081098/flowext/flowFromSuspend.kt @@ -0,0 +1,40 @@ +/* + * MIT License + * + * Copyright (c) 2021-2022 Petrus Nguyễn Thái Học + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.hoc081098.flowext + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow + +/** + * Creates a _cold_ flow that produces a single value from the given [function]. + * + * Example of usage: + * + * ``` + * suspend fun remoteCall(): R = ... + * fun remoteCallFlow(): Flow = flowFromSuspend(::remoteCall) + * ``` + */ +public fun flowFromSuspend(function: suspend () -> T): Flow = flow { emit(function()) } diff --git a/src/commonTest/kotlin/com/hoc081098/flowext/DeferTest.kt b/src/commonTest/kotlin/com/hoc081098/flowext/DeferTest.kt new file mode 100644 index 00000000..0c696a3a --- /dev/null +++ b/src/commonTest/kotlin/com/hoc081098/flowext/DeferTest.kt @@ -0,0 +1,70 @@ +/* + * MIT License + * + * Copyright (c) 2021-2022 Petrus Nguyễn Thái Học + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.hoc081098.flowext + +import com.hoc081098.flowext.utils.BaseTest +import com.hoc081098.flowext.utils.TestException +import com.hoc081098.flowext.utils.test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf +import kotlin.test.Test + +@ExperimentalCoroutinesApi +class DeferTest : BaseTest() { + @Test + fun deferredEmitsValues() = runTest { + var count = 0L + val flow = defer { + delay(count) + flowOf(count) + } + + flow.test(listOf(Event.Value(0L), Event.Complete)) + + ++count + flow.test(listOf(Event.Value(1L), Event.Complete)) + + ++count + flow.test(listOf(Event.Value(2L), Event.Complete)) + } + + @Test + fun deferFailureUpStream() = runTest { + val testException = TestException() + + defer { + flow { throw testException } + }.test(listOf(Event.Error(testException))) + } + + @Test + fun deferFactoryThrows() = runTest { + val testException = TestException() + + defer { throw testException }.test(listOf(Event.Error(testException))) + } +} diff --git a/src/commonTest/kotlin/com/hoc081098/flowext/FlowFromSuspendTest.kt b/src/commonTest/kotlin/com/hoc081098/flowext/FlowFromSuspendTest.kt new file mode 100644 index 00000000..3dbb6c5c --- /dev/null +++ b/src/commonTest/kotlin/com/hoc081098/flowext/FlowFromSuspendTest.kt @@ -0,0 +1,59 @@ +/* + * MIT License + * + * Copyright (c) 2021-2022 Petrus Nguyễn Thái Học + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.hoc081098.flowext + +import com.hoc081098.flowext.utils.BaseTest +import com.hoc081098.flowext.utils.TestException +import com.hoc081098.flowext.utils.test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.delay +import kotlin.test.Test + +@ExperimentalCoroutinesApi +class FlowFromSuspendTest : BaseTest() { + @Test + fun flowFromSuspendEmitsValues() = runTest { + var count = 0L + val flow = flowFromSuspend { + delay(count) + count + } + + flow.test(listOf(Event.Value(0L), Event.Complete)) + + ++count + flow.test(listOf(Event.Value(1L), Event.Complete)) + + ++count + flow.test(listOf(Event.Value(2L), Event.Complete)) + } + + @Test + fun deferFactoryThrows() = runTest { + val testException = TestException() + + flowFromSuspend { throw testException }.test(listOf(Event.Error(testException))) + } +} From 99706e9d32e6bee6172688b0d605b937a3d4fdaf Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Fri, 20 May 2022 10:04:28 +0000 Subject: [PATCH 2/3] chore(deps): update plugin org.jetbrains.kotlinx.binary-compatibility-validator to v0.10.0 --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 46617d78..656a9a27 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ plugins { id("com.diffplug.spotless") version "6.6.1" id("maven-publish") id("com.vanniktech.maven.publish") version "0.19.0" - id("org.jetbrains.kotlinx.binary-compatibility-validator") version "0.9.0" + id("org.jetbrains.kotlinx.binary-compatibility-validator") version "0.10.0" id("org.jetbrains.dokka") version "1.6.21" id("org.jetbrains.kotlinx.kover") version "0.5.1" } From 6a871414ead65f0baf8317d69e2cbae11f6a9854 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petrus=20Nguy=E1=BB=85n=20Th=C3=A1i=20H=E1=BB=8Dc?= Date: Fri, 20 May 2022 17:16:58 +0700 Subject: [PATCH 3/3] docs(defer) --- src/commonMain/kotlin/com/hoc081098/flowext/defer.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/commonMain/kotlin/com/hoc081098/flowext/defer.kt b/src/commonMain/kotlin/com/hoc081098/flowext/defer.kt index 0071124b..d5fb5ff9 100644 --- a/src/commonMain/kotlin/com/hoc081098/flowext/defer.kt +++ b/src/commonMain/kotlin/com/hoc081098/flowext/defer.kt @@ -25,7 +25,14 @@ package com.hoc081098.flowext import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.flow +/** + * Creates a [Flow] that, on collection, calls a [Flow] factory to make a [Flow] for each new [FlowCollector]. + * + * In some circumstances, waiting until the last minute (that is, until collection time) + * to generate the [Flow] can ensure that collectors receive the freshest data. + */ public fun defer(flowFactory: suspend () -> Flow): Flow = flow { emitAll(flowFactory()) }