
commonMain.flow.operators.Emitters.kt Maven / Gradle / Ivy
@file:JvmMultifileClass
@file:JvmName("FlowKt")
@file:Suppress("UNCHECKED_CAST")
package kotlinx.coroutines.flow
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.internal.*
import kotlin.jvm.*
// ------------------ WARNING ------------------
// These emitting operators must use safe flow builder, because they allow
// user code to directly emit to the underlying FlowCollector.
/**
* Applies [transform] function to each value of the given flow.
*
* The receiver of the `transform` is [FlowCollector] and thus `transform` is a
* flexible function that may transform emitted element, skip it or emit it multiple times.
*
* This operator generalizes [filter] and [map] operators and
* can be used as a building block for other operators, for example:
*
* ```
* fun Flow.skipOddAndDuplicateEven(): Flow = transform { value ->
* if (value % 2 == 0) { // Emit only even values, but twice
* emit(value)
* emit(value)
* } // Do nothing if odd
* }
* ```
*/
public inline fun Flow.transform(
@BuilderInference crossinline transform: suspend FlowCollector.(value: T) -> Unit
): Flow = flow { // Note: safe flow is used here, because collector is exposed to transform on each operation
collect { value ->
// kludge, without it Unit will be returned and TCE won't kick in, KT-28938
return@collect transform(value)
}
}
// For internal operator implementation
@PublishedApi
internal inline fun Flow.unsafeTransform(
@BuilderInference crossinline transform: suspend FlowCollector.(value: T) -> Unit
): Flow = unsafeFlow { // Note: unsafe flow is used here, because unsafeTransform is only for internal use
collect { value ->
// kludge, without it Unit will be returned and TCE won't kick in, KT-28938
return@collect transform(value)
}
}
/**
* Returns a flow that invokes the given [action] **before** this flow starts to be collected.
*
* The [action] is called before the upstream flow is started, so if it is used with a [SharedFlow]
* there is **no guarantee** that emissions from the upstream flow that happen inside or immediately
* after this `onStart` action will be collected
* (see [onSubscription] for an alternative operator on shared flows).
*
* The receiver of the [action] is [FlowCollector], so `onStart` can emit additional elements.
* For example:
*
* ```
* flowOf("a", "b", "c")
* .onStart { emit("Begin") }
* .collect { println(it) } // prints Begin, a, b, c
* ```
*/
public fun Flow.onStart(
action: suspend FlowCollector.() -> Unit
): Flow = unsafeFlow { // Note: unsafe flow is used here, but safe collector is used to invoke start action
val safeCollector = SafeCollector(this, currentCoroutineContext())
try {
safeCollector.action()
} finally {
safeCollector.releaseIntercepted()
}
collect(this) // directly delegate
}
/**
* Returns a flow that invokes the given [action] **after** the flow is completed or cancelled, passing
* the cancellation exception or failure as cause parameter of [action].
*
* Conceptually, `onCompletion` is similar to wrapping the flow collection into a `finally` block,
* for example the following imperative snippet:
*
* ```
* try {
* myFlow.collect { value ->
* println(value)
* }
* } finally {
* println("Done")
* }
* ```
*
* can be replaced with a declarative one using `onCompletion`:
*
* ```
* myFlow
* .onEach { println(it) }
* .onCompletion { println("Done") }
* .collect()
* ```
*
* Unlike [catch], this operator reports exception that occur both upstream and downstream
* and observe exceptions that are thrown to cancel the flow. Exception is empty if and only if
* the flow had fully completed successfully. Conceptually, the following code:
*
* ```
* myFlow.collect { value ->
* println(value)
* }
* println("Completed successfully")
* ```
*
* can be replaced with:
*
* ```
* myFlow
* .onEach { println(it) }
* .onCompletion { if (it == null) println("Completed successfully") }
* .collect()
* ```
*
* The receiver of the [action] is [FlowCollector] and this operator can be used to emit additional
* elements at the end **if it completed successfully**. For example:
*
* ```
* flowOf("a", "b", "c")
* .onCompletion { emit("Done") }
* .collect { println(it) } // prints a, b, c, Done
* ```
*
* In case of failure or cancellation, any attempt to emit additional elements throws the corresponding exception.
* Use [catch] if you need to suppress failure and replace it with emission of elements.
*/
public fun Flow.onCompletion(
action: suspend FlowCollector.(cause: Throwable?) -> Unit
): Flow = unsafeFlow { // Note: unsafe flow is used here, but safe collector is used to invoke completion action
try {
collect(this)
} catch (e: Throwable) {
/*
* Use throwing collector to prevent any emissions from the
* completion sequence when downstream has failed, otherwise it may
* lead to a non-sequential behaviour impossible with `finally`
*/
ThrowingCollector(e).invokeSafely(action, e)
throw e
}
// Normal completion
val sc = SafeCollector(this, currentCoroutineContext())
try {
sc.action(null)
} finally {
sc.releaseIntercepted()
}
}
/**
* Invokes the given [action] when this flow completes without emitting any elements.
* The receiver of the [action] is [FlowCollector], so `onEmpty` can emit additional elements.
* For example:
*
* ```
* emptyFlow().onEmpty {
* emit(1)
* emit(2)
* }.collect { println(it) } // prints 1, 2
* ```
*/
public fun Flow.onEmpty(
action: suspend FlowCollector.() -> Unit
): Flow = unsafeFlow {
var isEmpty = true
collect {
isEmpty = false
emit(it)
}
if (isEmpty) {
val collector = SafeCollector(this, currentCoroutineContext())
try {
collector.action()
} finally {
collector.releaseIntercepted()
}
}
}
/*
* 'emitAll' methods call this to fail-fast before starting to collect
* their sources (that may not have any elements for a long time).
*/
internal fun FlowCollector<*>.ensureActive() {
if (this is ThrowingCollector) throw e
}
internal class ThrowingCollector(@JvmField val e: Throwable) : FlowCollector {
override suspend fun emit(value: Any?) {
throw e
}
}
private suspend fun FlowCollector.invokeSafely(
action: suspend FlowCollector.(cause: Throwable?) -> Unit,
cause: Throwable?
) {
try {
action(cause)
} catch (e: Throwable) {
if (cause !== null && cause !== e) e.addSuppressed(cause)
throw e
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy