Skip to content

Commit 7d51152

Browse files
g000sha256dkhalanskyjb
authored andcommitted
Add the SharedFlow.asFlow operator (#4541)
See its documentation for the explanation and rationale. Fixes #4530
1 parent bec405b commit 7d51152

File tree

5 files changed

+65
-1
lines changed

5 files changed

+65
-1
lines changed

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ so do familiarize yourself with the following guidelines.
4747
PRs that add new API without a corresponding issue with positive feedback about the proposed implementation are
4848
unlikely to be approved or reviewed.
4949
* All new APIs must come with documentation and tests.
50-
* All new APIs are initially released with the `@ExperimentalCoroutineApi` annotation and graduate later.
50+
* All new APIs are initially released with the `@ExperimentalCoroutinesApi` annotation and graduate later.
5151
* [Update the public API dumps](#updating-the-public-api-dump) and commit the resulting changes as well.
5252
It will not pass the tests otherwise.
5353
* If you plan large API additions, then please start by submitting an issue with the proposed API design

kotlinx-coroutines-core/api/kotlinx-coroutines-core.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,6 +1012,7 @@ public final class kotlinx/coroutines/flow/FlowKt {
10121012
public static final fun asFlow (Lkotlin/ranges/IntRange;)Lkotlinx/coroutines/flow/Flow;
10131013
public static final fun asFlow (Lkotlin/ranges/LongRange;)Lkotlinx/coroutines/flow/Flow;
10141014
public static final fun asFlow (Lkotlin/sequences/Sequence;)Lkotlinx/coroutines/flow/Flow;
1015+
public static final fun asFlow (Lkotlinx/coroutines/flow/SharedFlow;)Lkotlinx/coroutines/flow/Flow;
10151016
public static final fun asFlow ([I)Lkotlinx/coroutines/flow/Flow;
10161017
public static final fun asFlow ([J)Lkotlinx/coroutines/flow/Flow;
10171018
public static final fun asFlow ([Ljava/lang/Object;)Lkotlinx/coroutines/flow/Flow;

kotlinx-coroutines-core/api/kotlinx-coroutines-core.klib.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1003,6 +1003,7 @@ final fun <#A: kotlin/Any?> (kotlinx.coroutines.flow/Flow<kotlinx.coroutines.flo
10031003
final fun <#A: kotlin/Any?> (kotlinx.coroutines.flow/Flow<kotlinx.coroutines.flow/Flow<#A>>).kotlinx.coroutines.flow/merge(): kotlinx.coroutines.flow/Flow<#A> // kotlinx.coroutines.flow/merge|merge@kotlinx.coroutines.flow.Flow<kotlinx.coroutines.flow.Flow<0:0>>(){0§<kotlin.Any?>}[0]
10041004
final fun <#A: kotlin/Any?> (kotlinx.coroutines.flow/MutableSharedFlow<#A>).kotlinx.coroutines.flow/asSharedFlow(): kotlinx.coroutines.flow/SharedFlow<#A> // kotlinx.coroutines.flow/asSharedFlow|asSharedFlow@kotlinx.coroutines.flow.MutableSharedFlow<0:0>(){0§<kotlin.Any?>}[0]
10051005
final fun <#A: kotlin/Any?> (kotlinx.coroutines.flow/MutableStateFlow<#A>).kotlinx.coroutines.flow/asStateFlow(): kotlinx.coroutines.flow/StateFlow<#A> // kotlinx.coroutines.flow/asStateFlow|asStateFlow@kotlinx.coroutines.flow.MutableStateFlow<0:0>(){0§<kotlin.Any?>}[0]
1006+
final fun <#A: kotlin/Any?> (kotlinx.coroutines.flow/SharedFlow<#A>).kotlinx.coroutines.flow/asFlow(): kotlinx.coroutines.flow/Flow<#A> // kotlinx.coroutines.flow/asFlow|asFlow@kotlinx.coroutines.flow.SharedFlow<0:0>(){0§<kotlin.Any?>}[0]
10061007
final fun <#A: kotlin/Any?> (kotlinx.coroutines.flow/SharedFlow<#A>).kotlinx.coroutines.flow/cancellable(): kotlinx.coroutines.flow/Flow<#A> // kotlinx.coroutines.flow/cancellable|cancellable@kotlinx.coroutines.flow.SharedFlow<0:0>(){0§<kotlin.Any?>}[0]
10071008
final fun <#A: kotlin/Any?> (kotlinx.coroutines.flow/SharedFlow<#A>).kotlinx.coroutines.flow/flowOn(kotlin.coroutines/CoroutineContext): kotlinx.coroutines.flow/Flow<#A> // kotlinx.coroutines.flow/flowOn|flowOn@kotlinx.coroutines.flow.SharedFlow<0:0>(kotlin.coroutines.CoroutineContext){0§<kotlin.Any?>}[0]
10081009
final fun <#A: kotlin/Any?> (kotlinx.coroutines.flow/SharedFlow<#A>).kotlinx.coroutines.flow/onSubscription(kotlin.coroutines/SuspendFunction1<kotlinx.coroutines.flow/FlowCollector<#A>, kotlin/Unit>): kotlinx.coroutines.flow/SharedFlow<#A> // kotlinx.coroutines.flow/onSubscription|onSubscription@kotlinx.coroutines.flow.SharedFlow<0:0>(kotlin.coroutines.SuspendFunction1<kotlinx.coroutines.flow.FlowCollector<0:0>,kotlin.Unit>){0§<kotlin.Any?>}[0]

kotlinx-coroutines-core/common/src/flow/operators/Share.kt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,34 @@ private fun <T> CoroutineScope.launchSharingDeferred(
353353
}
354354
}
355355

356+
// -------------------------------- asFlow --------------------------------
357+
358+
/**
359+
* Represents this shared flow and its subtypes as a plain flow,
360+
* hiding its hot flow characteristics.
361+
*
362+
* Unlike simple upcasting, the returned flow prevents casting back to the original
363+
* hot flow type, ensuring that implementation details remain encapsulated.
364+
*
365+
* Example:
366+
* ```
367+
* class Repository {
368+
* private val _updates = MutableStateFlow("initial")
369+
*
370+
* // Exposes Flow interface without leaking MutableStateFlow implementation
371+
* val updates: Flow<String> = _updates.asFlow()
372+
* }
373+
*
374+
* // Usage
375+
* val flow = repository.updates
376+
* val mutableStateFlow = flow as? MutableStateFlow // null - cast prevented
377+
* val stateFlow = flow as? StateFlow // null - cast prevented
378+
* ```
379+
*/
380+
@ExperimentalCoroutinesApi
381+
public fun <T> SharedFlow<T>.asFlow(): Flow<T> =
382+
transform { value -> emit(value) }
383+
356384
// -------------------------------- asSharedFlow/asStateFlow --------------------------------
357385

358386
/**
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
@file:Suppress("PackageDirectoryMismatch")
2+
3+
package kotlinx.coroutines.flow
4+
5+
import kotlinx.coroutines.testing.*
6+
import kotlin.test.*
7+
8+
class AsFlowTest : TestBase() {
9+
@Test
10+
fun testAsFlow() = runTest {
11+
val mutableSharedFlow = MutableSharedFlow<Int>(replay = 3)
12+
mutableSharedFlow.emit(value = 1)
13+
mutableSharedFlow.emit(value = 2)
14+
mutableSharedFlow.emit(value = 3)
15+
16+
val flow = mutableSharedFlow.asFlow()
17+
18+
assertEquals(
19+
expected = listOf(1, 2, 3),
20+
actual = flow
21+
.take(count = 3)
22+
.toList(),
23+
)
24+
}
25+
26+
@Test
27+
fun testAsFlowIsNotSharedFlow() {
28+
val mutableSharedFlow = MutableSharedFlow<Int>()
29+
30+
val flow = mutableSharedFlow.asFlow()
31+
32+
assertIsNot<SharedFlow<Int>>(flow)
33+
}
34+
}

0 commit comments

Comments
 (0)