Iterating over a Flow instead of using "collect"

Hey, I’m sure there’s a valid reason why Flow was designed this way and this might be a very newby question but I figured I’d ask anyway, I might learn something along the way.

I’ve noticed you can’t iterate over a Flow using a for loop the same way you can iterate over a Channel.
Which means we need to write code like this when we want to return something based on the result of the Flow:

public suspend fun <T> Flow<T>.first(): T {
    var result: Any? = NULL
    try {
        collect { value ->
            result = value
            throw AbortFlowException()
        }
    } catch (e: AbortFlowException) {
        // Do nothing
    }

    if (result === NULL) throw NoSuchElementException("Expected at least one element")
    return result as T
}

There’s nothing wrong with the code above but it would look nicer if it was:

public suspend fun <T> Flow<T>.first(): T {
        if (hasNext()) {
            return next() as T
        }  else {
           throw NoSuchElementException("Expected at least one element")
        }
}

Again, I’m only asking to learn more :slight_smile:
If I understand correctly this can be done with channels, I think of Flows as “simplier” channels as they don’t require synchronization. So, in theory, iterating over them with just a simple for loop should be viable?

1 Like

Hi @fernandoraviola,
take a look here to learn more about Flow: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html

Your idea is right, Flow is the suspending implementation of Reactive Stream, when a stream is subscribed (collect) the producer push down the stream as much data is possible (respecting the backpressure).

Much of the computation environment is obtained in collect invocation, it cannot change.
Moreover the Flow is a “one-to-one” stream and the iteration cannot left unfinished.
The Visitor Pattern (using collect) ensure all constraints.

Channel is a more flexible data structure but requires some extra cost, you have to consider a Channel when a Flow does not fit your use case.

Thanks for the clarification @fvasco

I’m not sure I understand what you mean by computation environment but I’ll make sure to read up on the link you provided to get more context on this.

Thanks again for your time!

CoroutineScope

Job, Dispatcher and so on…