From 5ae842cb52213c780f2aa60e341c1e97311390d5 Mon Sep 17 00:00:00 2001 From: Georgii Ippolitov Date: Mon, 13 Oct 2025 22:15:39 +0300 Subject: [PATCH 1/2] Add asFlow operator --- CONTRIBUTING.md | 2 +- .../api/kotlinx-coroutines-core.api | 1 + .../common/src/flow/operators/Share.kt | 9 +++++ .../common/test/flow/operators/AsFlowTest.kt | 34 +++++++++++++++++++ 4 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 kotlinx-coroutines-core/common/test/flow/operators/AsFlowTest.kt diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cc9c3bc998..98db4ecb51 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,7 +47,7 @@ so do familiarize yourself with the following guidelines. PRs that add new API without a corresponding issue with positive feedback about the proposed implementation are unlikely to be approved or reviewed. * All new APIs must come with documentation and tests. - * All new APIs are initially released with the `@ExperimentalCoroutineApi` annotation and graduate later. + * All new APIs are initially released with the `@ExperimentalCoroutinesApi` annotation and graduate later. * [Update the public API dumps](#updating-the-public-api-dump) and commit the resulting changes as well. It will not pass the tests otherwise. * If you plan large API additions, then please start by submitting an issue with the proposed API design diff --git a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api index 53a47b949e..91d5c1ab22 100644 --- a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api +++ b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api @@ -1012,6 +1012,7 @@ public final class kotlinx/coroutines/flow/FlowKt { public static final fun asFlow (Lkotlin/ranges/IntRange;)Lkotlinx/coroutines/flow/Flow; public static final fun asFlow (Lkotlin/ranges/LongRange;)Lkotlinx/coroutines/flow/Flow; public static final fun asFlow (Lkotlin/sequences/Sequence;)Lkotlinx/coroutines/flow/Flow; + public static final fun asFlow (Lkotlinx/coroutines/flow/SharedFlow;)Lkotlinx/coroutines/flow/Flow; public static final fun asFlow ([I)Lkotlinx/coroutines/flow/Flow; public static final fun asFlow ([J)Lkotlinx/coroutines/flow/Flow; public static final fun asFlow ([Ljava/lang/Object;)Lkotlinx/coroutines/flow/Flow; diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Share.kt b/kotlinx-coroutines-core/common/src/flow/operators/Share.kt index 994967b3a8..8b54ca2f9c 100644 --- a/kotlinx-coroutines-core/common/src/flow/operators/Share.kt +++ b/kotlinx-coroutines-core/common/src/flow/operators/Share.kt @@ -353,6 +353,15 @@ private fun CoroutineScope.launchSharingDeferred( } } +// -------------------------------- asFlow -------------------------------- + +/** + * Represents this shared flow as a flow. + */ +@ExperimentalCoroutinesApi +public fun SharedFlow.asFlow(): Flow = + transform { value -> emit(value) } + // -------------------------------- asSharedFlow/asStateFlow -------------------------------- /** diff --git a/kotlinx-coroutines-core/common/test/flow/operators/AsFlowTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/AsFlowTest.kt new file mode 100644 index 0000000000..e38e9cba0b --- /dev/null +++ b/kotlinx-coroutines-core/common/test/flow/operators/AsFlowTest.kt @@ -0,0 +1,34 @@ +@file:Suppress("PackageDirectoryMismatch") + +package kotlinx.coroutines.flow + +import kotlinx.coroutines.testing.* +import kotlin.test.* + +class AsFlowTest : TestBase() { + @Test + fun testAsFlow() = runTest { + val mutableSharedFlow = MutableSharedFlow(replay = 3) + mutableSharedFlow.emit(value = 1) + mutableSharedFlow.emit(value = 2) + mutableSharedFlow.emit(value = 3) + + val flow = mutableSharedFlow.asFlow() + + assertEquals( + expected = listOf(1, 2, 3), + actual = flow + .take(count = 3) + .toList(), + ) + } + + @Test + fun testAsFlowIsNotSharedFlow() { + val mutableSharedFlow = MutableSharedFlow() + + val flow = mutableSharedFlow.asFlow() + + assertIsNot>(flow) + } +} \ No newline at end of file From 79c247ec1583c4c7a0d18800967807121c14155e Mon Sep 17 00:00:00 2001 From: Georgii Ippolitov Date: Tue, 14 Oct 2025 22:04:12 +0300 Subject: [PATCH 2/2] Improve method documentation --- .../common/src/flow/operators/Share.kt | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Share.kt b/kotlinx-coroutines-core/common/src/flow/operators/Share.kt index 8b54ca2f9c..183501660f 100644 --- a/kotlinx-coroutines-core/common/src/flow/operators/Share.kt +++ b/kotlinx-coroutines-core/common/src/flow/operators/Share.kt @@ -356,7 +356,26 @@ private fun CoroutineScope.launchSharingDeferred( // -------------------------------- asFlow -------------------------------- /** - * Represents this shared flow as a flow. + * Represents this shared flow and its subtypes as a plain flow, + * hiding its hot flow characteristics. + * + * Unlike simple upcasting, the returned flow prevents casting back to the original + * hot flow type, ensuring that implementation details remain encapsulated. + * + * Example: + * ``` + * class Repository { + * private val _updates = MutableStateFlow("initial") + * + * // Exposes Flow interface without leaking MutableStateFlow implementation + * val updates: Flow = _updates.asFlow() + * } + * + * // Usage + * val flow = repository.updates + * val mutableStateFlow = flow as? MutableStateFlow // null - cast prevented + * val stateFlow = flow as? StateFlow // null - cast prevented + * ``` */ @ExperimentalCoroutinesApi public fun SharedFlow.asFlow(): Flow =