What are some actual use cases for simple Flows? B...
# coroutines
r
What are some actual use cases for simple Flows? By simple I mean not a SharedFlow or StateFlow exposed as a Flow. Just an actual Flow, a cold flow basically I guess
k
Lots of use cases. Most intermediate operators are written and can be written as a transformation with the
flow { … }
builder. registering callbacks to a flow can be done with
callbackFlow
. Collecting many flows concurrently can be done with a
channelFlow
j
Observing a database query
Connecting to a websocket API
Monitoring device location changes
Extracting the frames of a video file
k
There was a good article posted here recently about the differences between “hot” flows and “cold” flows. https://betterprogramming.pub/stop-calling-kotlin-flows-hot-and-cold-48e87708d863 My personal preference is to prefer regular flows when I can use them, and only use State or Shared flows if I need them.
Oh, also, if you ever need a flow to complete successfully for whatever reason you simply cannot use shared or state flows
r
Thanks. I'm working on some kind of course for colleagues and I realize that 1) I don't truly understand flows in general, I just know how to apply them to the use cases I've been having, not much else, 2) I really lack knowledge on every kind of flows and channels there are and when to use them and 3) as stated in the article you linked Kevin, the hot and cold stuff is pretty confusing as it doesn't really match
k
yeah hot and cold is bad terminology
1
especially because things like
Channel.consumeAsFlow
is hot but it’s not entirely clear that it is
r
Like, Flow = cold is just false because some Flows are hot, SharedFlow = hot is kinda wrong too because, yes a SharedFlow might be hot but it's not called HotFlow for a reason I guess. It should just be seen as a Flow that can have multiple collectors. Talking about hot and cold feels like the wrong angle to approach them, yet the documentation of SharedFlow literally starts there…
I really think that Flows are simple. But writing a course making you feel like they are simple is hard
😅 1
c
A helpful distinction is that there’s a 1-to-1 relationship between the emitter and collector of a cold flow. The emitter sends values into the Flow which directly go to the Collector through the Flow pipeline. Thus, the flow only exists when both the emitter and collector are connected to one another, and is somewhat imaginary. There’s not really a physical object connecting the emitter and collector together other, it’s just a clever application of suspend functions. Hot flows, on the other hand, allow either multiple emitters to send values to a single collector; one emitter to share values with multiple collectors, or both. There necessarily must be some object that coordinates between the multiple emitters and/or collectors. This object (typically a Channel or Shared/StateFlow) necessarily lives outside of the presence of any emitter or collector, and must be managed on its own. Ultimately, “hot flows” work by essentially creating a new “cold flow” for each subscriber, making a 1-to-1 connection from the Channel or Shared/StateFlow as the emitter to the collector.
k
A helpful distinction is that there’s a 1-to-1 relationship between the emitter and collector of a cold flow. The emitter sends values into the Flow which directly go to the Collector through the Flow pipeline. Thus, the flow only exists when both the emitter and collector are connected to one another, and is somewhat imaginary. There’s not really a physical object connecting the emitter and collector together other, it’s just a clever application of suspend functions.
Does this apply for constructs like
channelFlow
though? Consider the example where you register a callback to some API that sends values to a channel.
c
Yes, a channel flow is a cold flow. Only the stuff within the channel flow’s lambda is able to send values downstream. Technically you can register multiple callbacks within that scope, but it is still strictly limited to that single scope, meaning only that single point of your code is able to work as the emitter.
k
Right, but there’s still an object connecting the callback and the flow together — a Channel. That’s why I really like the framing set out in the above post: > When multiple coroutines are consuming or producing at the same time, a channel is the communication tool they use to distribute and coordinate their work. But with proper use of flows and structured concurrency, the channel and all its coroutines can still be wrapped up and encapsulated so that the rest of the application doesn’t have to worry about them. It’s mostly about encapsulation and not hot or cold
j
The channel only connects the callback and the emitter
👍 1
c
Technically there is a channel, but it doesn’t change the fact that the flow is still cold. The same logic holds true for the normal flow builder as well. You can launch multiple coroutines within a
flow { }
block having multiple things emitting values. But there’s still a one-to-one connection from the
flow { }
builder to the collector. The only difference with a callbackFlow is whether the callbacks producing values are suspending vs not suspending
j
The emitter is what's consuming the channel–not the collector
👍 1
k
Yes okay consider me convinced. Also, updated my above post to not mislead people.
c
Conceptually, a
channelFlow
is effectively just this:
Copy code
flow { 
    val channel = Channel()
    registerCallback { valueFromCallback -> channel.trySend(valueFromCallback) }

    for(value in channel) { 
        emit(value)
    }
}
k
Yes. What really tripped me up was this statement: > There’s not really a physical object connecting the emitter and collector together other, it’s just a clever application of suspend functions. I had misunderstood that the channel was connected to the emitter when considered in this discussion, and not the collector. In practice with the above example it’s painfully obvious, and now I think your rule of thumb for 1 collector to 1 emitter is really good
c
It’s actually quite literal, too. A FlowCollector is just a lambda, and a flow created with
flow { }
directly executes that lambda. Even when considering buffering and other flow operators, that just sticks stuff between those two directly-connected points
👍 1