Skip to content

Commit bb7b3c2

Browse files
qwwdfsadelizarov
authored andcommitted
Provide CoroutineScope into flowViaChannel block, but make it non-suspending.
* It allows using flowViaChannel for integration with Java callbacks * CoroutineScope is provided to provide a lifecycle object (that is not otherwise accessible) * Suspending use-cases are covered with flow builder Fixes #1081
1 parent be467e3 commit bb7b3c2

File tree

2 files changed

+62
-9
lines changed

2 files changed

+62
-9
lines changed

kotlinx-coroutines-core/common/src/flow/Builders.kt

+5-5
Original file line numberDiff line numberDiff line change
@@ -185,18 +185,17 @@ public fun LongRange.asFlow(): Flow<Long> = flow {
185185
/**
186186
* Creates an instance of the cold [Flow] with elements that are sent to a [SendChannel]
187187
* that is provided to the builder's [block] of code. It allows elements to be
188-
* produced by the code that is running in a different context,
189-
* e.g. from a callback-based API.
188+
* produced by the code that is running in a different context, e.g. from a callback-based API.
190189
*
191190
* The resulting flow is _cold_, which means that [block] is called on each call of a terminal operator
192-
* on the resulting flow.
191+
* on the resulting flow. The [block] is not suspending deliberately, if you need suspending scope, [flow] builder
192+
* should be used instead.
193193
*
194194
* To control backpressure, [bufferSize] is used and matches directly the `capacity` parameter of [Channel] factory.
195195
* The provided channel can later be used by any external service to communicate with flow and its buffer determines
196196
* backpressure buffer size or its behaviour (e.g. in case when [Channel.CONFLATED] was used).
197197
*
198198
* Example of usage:
199-
*
200199
* ```
201200
* fun flowFrom(api: CallbackBasedApi): Flow<T> = flowViaChannel { channel ->
202201
* val callback = object : Callback { // implementation of some callback interface
@@ -206,6 +205,7 @@ public fun LongRange.asFlow(): Flow<Long> = flow {
206205
* override fun onApiError(cause: Throwable) {
207206
* channel.cancel("API Error", CancellationException(cause))
208207
* }
208+
* override fun onCompleted() = channel.close()
209209
* }
210210
* api.register(callback)
211211
* channel.invokeOnClose {
@@ -217,7 +217,7 @@ public fun LongRange.asFlow(): Flow<Long> = flow {
217217
@FlowPreview
218218
public fun <T> flowViaChannel(
219219
bufferSize: Int = 16,
220-
@BuilderInference block: suspend (SendChannel<T>) -> Unit
220+
@BuilderInference block: CoroutineScope.(channel: SendChannel<T>) -> Unit
221221
): Flow<T> {
222222
return flow {
223223
coroutineScope {

kotlinx-coroutines-core/common/test/flow/channels/FlowViaChannelTest.kt

+57-4
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,20 @@ class FlowViaChannelTest : TestBase() {
1212
@Test
1313
fun testRegular() = runTest {
1414
val flow = flowViaChannel<Int> {
15-
it.send(1)
16-
it.send(2)
15+
assertTrue(it.offer(1))
16+
assertTrue(it.offer(2))
17+
assertTrue(it.offer(3))
18+
it.close()
19+
}
20+
assertEquals(listOf(1, 2, 3), flow.toList())
21+
}
22+
23+
@Test
24+
fun testBuffer() = runTest {
25+
val flow = flowViaChannel<Int>(bufferSize = 1) {
26+
assertTrue(it.offer(1))
27+
assertTrue(it.offer(2))
28+
assertFalse(it.offer(3))
1729
it.close()
1830
}
1931
assertEquals(listOf(1, 2), flow.toList())
@@ -22,10 +34,51 @@ class FlowViaChannelTest : TestBase() {
2234
@Test
2335
fun testConflated() = runTest {
2436
val flow = flowViaChannel<Int>(bufferSize = Channel.CONFLATED) {
25-
it.send(1)
26-
it.send(2)
37+
assertTrue(it.offer(1))
38+
assertTrue(it.offer(2))
2739
it.close()
2840
}
2941
assertEquals(listOf(1), flow.toList())
3042
}
43+
44+
@Test
45+
fun testFailureCancelsChannel() = runTest {
46+
val flow = flowViaChannel<Int> {
47+
it.offer(1)
48+
it.invokeOnClose {
49+
expect(2)
50+
}
51+
}.onEach { throw TestException() }
52+
53+
expect(1)
54+
assertFailsWith<TestException>(flow)
55+
finish(3)
56+
}
57+
58+
@Test
59+
fun testFailureInSourceCancelsConsumer() = runTest {
60+
val flow = flowViaChannel<Int> {
61+
expect(2)
62+
throw TestException()
63+
}.onEach { expectUnreached() }
64+
65+
expect(1)
66+
assertFailsWith<TestException>(flow)
67+
finish(3)
68+
}
69+
70+
@Test
71+
fun testScopedCancellation() = runTest {
72+
val flow = flowViaChannel<Int> {
73+
expect(2)
74+
launch(start = CoroutineStart.ATOMIC) {
75+
hang { expect(3) }
76+
}
77+
throw TestException()
78+
}.onEach { expectUnreached() }
79+
80+
expect(1)
81+
assertFailsWith<TestException>(flow)
82+
finish(4)
83+
}
3184
}

0 commit comments

Comments
 (0)