@@ -16,14 +16,38 @@ import kotlin.jvm.*
16
16
* **Preview feature**: The design of `stateIn` operator is tentative and could change.
17
17
*/
18
18
@FlowPreview
19
- public interface StateFlowJob <T > : StateFlow <T >, Job
19
+ public interface StateFlowJob <out T > : StateFlow <T >, Job
20
20
21
21
/* *
22
- * Launches a coroutine that collects this [Flow] and emits all the collected values as a resulting [StateFlow].
22
+ * Launches a coroutine that collects this [Flow] and emits all the collected values as the resulting [StateFlow].
23
+ * The result of this function is both a [StateFlow] and a [Job]. This call effectively turns a cold [Flow] into a
24
+ * hot, active [Flow], making the most recently emitted value available for consumption at any time via
25
+ * [StateFlow.value]. This function returns immediately. The state flow it creates is initially set
26
+ * to the specified default [value]. As an alternative a version of `stateIn` without a default value can be used,
27
+ * which suspend until the source flow emits a first value.
28
+ *
29
+ * The resulting coroutine can be cancelled by [cancelling][CoroutineScope.cancel] the [scope] in which it is
30
+ * launched or by [cancelling][Job.cancel] the resulting [Job].
31
+ *
32
+ * Errors in the source flow are not propagated to the [scope] but [close][MutableStateFlow.close] the resulting
33
+ * [StateFlow] or are rethrown to the caller of `stateIn` if they happen before emission of the first value.
34
+ *
35
+ * **Preview feature**: The design of `stateIn` operator is tentative and could change.
36
+ */
37
+ @FlowPreview
38
+ public fun <T > Flow<T>.stateIn (scope : CoroutineScope , value : T ): StateFlowJob <T > {
39
+ val state = StateFlow (value)
40
+ val job = scope.launchStateJob(this , null , state)
41
+ return StateFlowJobImpl (state, job)
42
+ }
43
+
44
+ /* *
45
+ * Launches a coroutine that collects this [Flow] and emits all the collected values as the resulting [StateFlow].
23
46
* The result of this function is both a [StateFlow] and a [Job]. This call effectively turns a cold [Flow] into a
24
47
* hot, active [Flow], making the most recently emitted value available for consumption at any time via
25
48
* [StateFlow.value]. This call suspends until the source flow the emits first value and
26
- * throws [NoSuchElementException] if the flow was empty.
49
+ * throws [NoSuchElementException] if the flow was empty. As an alternative, a version of `stateIn` with a default
50
+ * value can be used, which does not suspend.
27
51
*
28
52
* The resulting coroutine can be cancelled by [cancelling][CoroutineScope.cancel] the [scope] in which it is
29
53
* launched or by [cancelling][Job.cancel] the resulting [Job].
@@ -35,32 +59,38 @@ public interface StateFlowJob<T> : StateFlow<T>, Job
35
59
*/
36
60
@FlowPreview
37
61
public suspend fun <T > Flow<T>.stateIn (scope : CoroutineScope ): StateFlowJob <T > {
38
- val result = CompletableDeferred <StateFlowJob <T >>()
39
- scope.launch {
40
- var state: MutableStateFlow <T >? = null
41
- var exception: Throwable ? = null
42
- try {
43
- collect { value ->
44
- // Update state flow if initialized
45
- state?.let { it.value = value } ? : run {
46
- // Or create state on the first value if state was not created yet (first value)
47
- state = StateFlow (value).also {
48
- // resume stateIn call with initialized StateFlow and current job
49
- result.complete(StateFlowJobImpl (it, coroutineContext[Job ]!! ))
50
- }
62
+ val deferredResult = CompletableDeferred <StateFlowJob <T >>()
63
+ scope.launchStateJob(this , deferredResult, null )
64
+ return deferredResult.await() // tail call
65
+ }
66
+
67
+ private fun <T > CoroutineScope.launchStateJob (
68
+ flow : Flow <T >,
69
+ deferredResult : CompletableDeferred <StateFlowJob <T >>? ,
70
+ initialState : MutableStateFlow <T >?
71
+ ) = launch {
72
+ var state: MutableStateFlow <T >? = initialState
73
+ var exception: Throwable ? = null
74
+ try {
75
+ flow.collect { value ->
76
+ // Update state flow if initialized
77
+ state?.let { it.value = value } ? : run {
78
+ // Or create state on the first value if state was not created yet (first value)
79
+ state = StateFlow (value).also {
80
+ // resume stateIn call with initialized StateFlow and current job
81
+ deferredResult?.complete(StateFlowJobImpl (it, coroutineContext[Job ]!! ))
51
82
}
52
83
}
53
- } catch (e: Throwable ) {
54
- // Ignore cancellation exception -- it is a normal way to stop the flow
55
- if (e !is CancellationException ) exception = e
56
- }
57
- // Close the state flow with exception if initialized
58
- state?.apply { close(exception) } ? : run {
59
- // Or complete the deferred exceptionally if the state was not create yet)
60
- result.completeExceptionally(exception ? : NoSuchElementException (" Expected at least one element" ))
61
84
}
85
+ } catch (e: Throwable ) {
86
+ // Ignore cancellation exception -- it is a normal way to stop the flow
87
+ if (e !is CancellationException ) exception = e
88
+ }
89
+ // Close the state flow with exception if initialized
90
+ state?.apply { close(exception) } ? : run {
91
+ // Or complete the deferred exceptionally if the state was not create yet)
92
+ deferredResult?.completeExceptionally(exception ? : NoSuchElementException (" Expected at least one element" ))
62
93
}
63
- return result.await() // tail call
64
94
}
65
95
66
96
private class StateFlowJobImpl <T >(state : StateFlow <T >, job : Job ) : StateFlowJob<T>, StateFlow<T> by state, Job by job
0 commit comments