Chapter 16: Flows
Introduces flows as a coroutine-based way to work with sequential values over time.
After this chapter you will be able to
- Flows as sequential streams. Explain that flows are a higher-level abstraction built on coroutines for working with multiple sequential values over time.
- Values available over time. Explain why a suspending function that returns a collection may be unsuitable when its elements become available at different times, and how a flow addresses that need.
- Processing values as emitted. Explain how the example creates and collects a flow, and why its output appears before all values have been computed.
- Cold and hot flow distinction. Distinguish cold flows from hot flows by explaining when each produces items and whether a collector must be consuming them.
- Flow builder and emissions. Describe how the flow builder defines a stream of values: its block can call emit to offer a value to the collector, and can call suspending functions such as delay.
- Cold flow execution timing. Explain why calling flow produces no output by itself and what starts execution of the work defined in its builder.
- Collection and flow execution. Explain that a cold flow’s code runs when collect is called and that the collector’s lambda runs for each emission. Describe why collect and its lambda can suspend.
- Repeated and unending collection. Explain what happens when the same cold flow is collected multiple times and why collection of an infinite flow must be stopped to return.
- Cancelling a flow collector. Explain that cancelling the collector’s coroutine stops collection at the next cancellation point, and that emit is one such point.
- Cold flow execution path. Trace what happens when a cold flow is collected: how the flow builder’s lambda can call emit, how emit passes a value to the collector’s lambda, and where execution resumes afterward.
- Sequential cold flow emissions. Explain why independent computations in a regular cold flow run one after another and why launching coroutines to call emit does not make their emissions concurrent.
- Concurrent channel flow production. Describe how channelFlow lets independent computations produce values concurrently while collection remains sequential.
- Flow builder selection. Justify choosing a regular cold flow by default and a channel flow when the flow needs to launch coroutines for concurrent operations.
- Shared emissions independent of subscribers. Explain how hot flows differ from cold flows: hot flows share emitted items among subscribers, and emissions can happen even when no subscriber is present.
- Shared flow broadcasting. Explain that a shared flow can emit values without collectors and broadcasts each new value to its current subscribers. Distinguish subscribers from the coroutine that produces emissions.
- Cold flow sharing with shareIn. Explain why multiple collectors of a cold flow may duplicate work and how shareIn converts it to a hot shared flow. Describe what its scope and started options control.
- State flow creation and access. Describe how to create and expose a state flow, and how callers read its current value or collect changes.
- Atomic state flow updates. Explain why incrementing the value property with ++ can lose updates across coroutines and how update avoids that problem.
- Equality-based emission conflation. Explain when a state flow emits to collectors after its value is set.
- Shared and state flow behavior. Distinguish shared flows, which broadcast events to current subscribers, from state flows, which represent a single state value and emit when that value changes.
- Late subscriber message history. Explain why the delayed subscriber in the message-broadcast example prints nothing, and describe how storing the entire message history as a list in a state flow lets later collectors access it.
- Choosing flows by purpose. Explain why a function that provides a service, such as a network request or database read, is generally declared using a cold flow, and how its callers may use that flow.
What it builds on
- Concurrency the Kotlin way: Suspending functions and coroutines (chapter 14) · Introduces coroutines as a Kotlin concurrency abstraction.
- Functions that can pause: Suspending functions (chapter 14) · Suspending functions can pause during execution.
- Executing sequence operations: Intermediate and terminal operations (chapter 6) · The distinction between defining lazy operations and executing them.
- Cancellation (chapter 15) · Assumes the coroutine cancellation mechanisms introduced earlier.
- Creating a coroutine scope: The coroutineScope function (chapter 15) · Using a coroutine scope to launch coroutines
- Creating start-and-forget coroutines: The launch function (chapter 14) · Uses launching a coroutine to perform work.
- Coroutine scopes establish structure between coroutines (chapter 15) · Uses a coroutine scope to structure the lifetime of the sharing coroutine.
Section by section
Introduction
Introduces flows as a coroutine-based way to work with sequential values over time.
- Flows as sequential streams: Explain that flows are a higher-level abstraction built on coroutines for working with multiple sequential values over time.
Why it matters: This gives you the basic idea behind the flow types and operations discussed next.
Key terms: flows, coroutines, sequential values
16.1 Flows model sequential streams of values
This section explains why flows are useful when a computation produces values over time instead of returning them all at once.
- Values available over time: Explain why a suspending function that returns a collection may be unsuitable when its elements become available at different times, and how a flow addresses that need.
Why it matters: It explains the problem flows solve that simply returning a list does not. - General-purpose flow uses: Describe flows as a coroutine-based abstraction for values that appear over time, and give examples of the kinds of functionality they can model.
Why it matters: These examples show that flows serve more than one kind of ongoing computation.
Key terms: suspending function, flows, progressive loading, streams of events, subscription-style APIs
16.1.1 Flows allow you to work with elements as they are emitted
This section shows how a flow lets code process values as they are emitted instead of waiting for an entire batch.
- Processing values as emitted: Explain how the example creates and collects a flow, and why its output appears before all values have been computed.
Why it matters: The key benefit illustrated here is that you can work with each value without waiting for the whole batch.
Key terms: flow builder function, emit, collect, flows
16.1.2 Different types of flows in Kotlin
This subsection distinguishes cold and hot flows by when they produce values and whether production depends on a collector.
- Cold and hot flow distinction: Distinguish cold flows from hot flows by explaining when each produces items and whether a collector must be consuming them.
Why it matters: This distinction helps you choose the right mental model for a flow’s values.
Key terms: cold flows, hot flows, collector, broadcast
16.2 Cold flows
This introduction previews a closer look at creating, running, cancelling, and using concurrency in cold flows.
16.2.1 Creating a cold flow with the flow builder function
This section shows how to create a cold flow with the flow builder and explains why its work waits until collection.
- Flow builder and emissions: Describe how the flow builder defines a stream of values: its block can call emit to offer a value to the collector, and can call suspending functions such as delay.
Why it matters: It explains how a flow produces values while allowing suspending work between emissions. - Cold flow execution timing: Explain why calling flow produces no output by itself and what starts execution of the work defined in its builder.
Why it matters: It prevents the mistaken expectation that merely creating a flow starts producing values. - Consequences of deferred execution: Explain why a non-suspending function can return a cold flow whose builder calls suspending functions, and why defining an infinite flow does not start its loop immediately.
Why it matters: Deferred execution lets ordinary functions describe streams that will perform suspending or ongoing work later.
Key terms: flow builder, emit, collector, suspend, Flow<T>, cold, collected, terminal operator, cold flow, non-suspending
16.2.2 Cold flows don’t do any work until collected
This section explains that collecting a cold flow runs its code, how collection and emission proceed together, and what repeated or unending collection implies.
- Collection and flow execution: Explain that a cold flow’s code runs when collect is called and that the collector’s lambda runs for each emission. Describe why collect and its lambda can suspend.
Why it matters: It explains why a flow does no work merely by being defined and why processing an emitted value can pause collection. - Interleaved emission and collection: Trace the example’s sequence from an emission through the collector’s delay and back to the flow’s delay before the next emission.
Why it matters: The order accounts for the roughly 700-millisecond gap between collected elements in the example. - Repeated and unending collection: Explain what happens when the same cold flow is collected multiple times and why collection of an infinite flow must be stopped to return.
Why it matters: Repeated collection can repeat external work, while an infinite flow will not finish collecting on its own.
Key terms: collect, Flow, collector, emission, suspending function, flow builder, delay, cold flow, side effects, cancel
16.2.3 Cancelling the collection of a flow
This subsection explains how cancelling a collector’s coroutine stops flow collection.
- Cancelling a flow collector: Explain that cancelling the collector’s coroutine stops collection at the next cancellation point, and that emit is one such point.
Why it matters: It explains when cancellation of a collecting coroutine actually stops a flow.
Key terms: flow collector, coroutine, cancellation point, emit
16.2.4 Cold flows under the hood
Explains how a cold flow uses a builder lambda and a collector lambda to emit values during collection.
- Cold flow execution path: Trace what happens when a cold flow is collected: how the flow builder’s lambda can call emit, how emit passes a value to the collector’s lambda, and where execution resumes afterward.
Why it matters: This call sequence explains how a cold flow produces values for its collector without much machinery.
Key terms: cold flow, flow builder function, Flow, FlowCollector, collect, emit
16.2.5 Concurrent flows with channel flows
This section explains why regular cold flows emit sequentially, how channel flows allow concurrent work, and when the extra complexity is worthwhile.
- Sequential cold flow emissions: Explain why independent computations in a regular cold flow run one after another and why launching coroutines to call emit does not make their emissions concurrent.
Why it matters: This restriction explains both the performance bottleneck and why a different builder is needed for concurrent work. - Concurrent channel flow production: Describe how channelFlow lets independent computations produce values concurrently while collection remains sequential.
Why it matters: It allows independent work to overlap without requiring the collector to process multiple values at once. - Flow builder selection: Justify choosing a regular cold flow by default and a channel flow when the flow needs to launch coroutines for concurrent operations.
Why it matters: Choosing the simpler builder avoids paying for concurrency machinery when the flow does not need it.
Key terms: flow builder function, emit, flow invariant, FlowCollector, channelFlow, channel flow, send, collector, coroutine scope, regular cold flows
16.3 Hot flows
Introduces hot flows as streams that share emissions among subscribers and identifies Kotlin’s two hot-flow types.
- Shared emissions independent of subscribers: Explain how hot flows differ from cold flows: hot flows share emitted items among subscribers, and emissions can happen even when no subscriber is present.
Why it matters: This distinction explains why hot flows suit events or state changes that are not tied to a collector being present. - Shared and state flow roles: Distinguish the two hot-flow implementations named here: shared flows broadcast values, while state flows communicate state.
Why it matters: These roles give you a starting point for choosing which hot-flow type fits what you are communicating.
Key terms: hot flows, cold flows, collectors, subscribers, emissions, Shared flows, State flows, broadcasting values, communicating state
16.3.1 Shared flows broadcast values to subscribers
This section explains how shared flows broadcast values, how subscribers receive and replay them, and how to share the work of a cold flow.
- Shared flow broadcasting: Explain that a shared flow can emit values without collectors and broadcasts each new value to its current subscribers. Distinguish subscribers from the coroutine that produces emissions.
Why it matters: It explains why several collectors can listen to one ongoing stream without each starting its production. - Mutable flow encapsulation: Describe how the RadioStation keeps a private mutable shared flow for emitting values, exposes a read-only SharedFlow for subscribers, and starts emissions in a coroutine.
Why it matters: Consumers can listen without being given the ability to broadcast their own values. - Subscriber timing and replay: Explain what a new subscriber receives by default and how the replay parameter changes that behavior.
Why it matters: It lets you decide whether late subscribers need recent values or only future broadcasts. - Cold flow sharing with shareIn: Explain why multiple collectors of a cold flow may duplicate work and how shareIn converts it to a hot shared flow. Describe what its scope and started options control.
Why it matters: Sharing avoids repeating sensor queries or other costly work for each collector while keeping that work tied to a coroutine scope.
Key terms: SharedFlow, subscriber, collector, broadcast, MutableSharedFlow, _messageFlow, messageFlow, beginBroadcasting, collect, replay
16.3.2 Keeping track of state in your system: State flow
This section explains how state flows hold a changing value, support safe updates, emit only changed values, and can be created from cold flows.
- State flow creation and access: Describe how to create and expose a state flow, and how callers read its current value or collect changes.
Why it matters: A state flow lets callers get the current state as well as observe its changes. - Atomic state flow updates: Explain why incrementing the value property with ++ can lose updates across coroutines and how update avoids that problem.
Why it matters: Safe reads alone do not prevent lost writes when several coroutines change the same state. - Equality-based emission conflation: Explain when a state flow emits to collectors after its value is set.
Why it matters: A collector observes changes in state, not every attempt to set it. - Cold flow state conversion: Describe what stateIn provides when converting a cold flow to a state flow and how its upstream execution relates to collectors and the coroutine scope.
Why it matters: Conversion makes the latest result of a cold flow available as shared state without rerunning the upstream flow for each reader.
Key terms: state flow, MutableStateFlow, StateFlow, value, collect, update, atomic updates, equality-based conflation, collectors, stateIn
16.3.3 Comparing state flows and shared flows
Compares shared flows and state flows, then shows how storing message history as state can serve subscribers who arrive late.
- Shared and state flow behavior: Distinguish shared flows, which broadcast events to current subscribers, from state flows, which represent a single state value and emit when that value changes.
Why it matters: The distinction helps you choose between broadcasting events and representing current state. - Late subscriber message history: Explain why the delayed subscriber in the message-broadcast example prints nothing, and describe how storing the entire message history as a list in a state flow lets later collectors access it.
Why it matters: It shows when changing what you store can be simpler than coordinating subscribers with broadcasts.
Key terms: hot flows, shared flows, state flows, equality-based conflation, subscriber, shared flow, state flow, message history, replay cache
16.3.4 Hot, cold, shared, state: When to use which flow
This section gives a rule of thumb for choosing cold versus hot flows and notes how Kotlin flows can interoperate with other reactive-stream implementations.
- Choosing flows by purpose: Explain why a function that provides a service, such as a network request or database read, is generally declared using a cold flow, and how its callers may use that flow.
Why it matters: This rule of thumb helps you choose how to provide data and how to share it with other parts of a system. - Reactive stream interoperability: Describe how Kotlin flows can be used in a project that already uses another reactive-stream abstraction.
Why it matters: Existing reactive-stream code need not prevent you from using Kotlin flows.
Key terms: cold flows, state flows, shared flows, collect, built-in conversion functions, Kotlin flows, reactive streams
16.4 Summary
Where this is used later
- Manipulating flows with flow operators (chapter 17) · Flows as streams of sequential values over time.
- Filtering out values on a timeout: The debounce operator (chapter 17) · Flow elements can be processed as they are emitted.
- Creating custom intermediate operators (chapter 17) · Creating a cold flow with the flow builder.
- Terminal operators execute the upstream flow and may compute a value (chapter 17) · It establishes that a terminal operator starts execution of the upstream flow.
- The take operator family can cancel a flow (chapter 17) · Introduces another way to cancel flow execution using take.
- Intermediate operators are applied to an upstream flow and return a downstream flow (chapter 17) · Understanding when a flow runs and how values reach a collector provides a basis for following intermediate flow operators.
- Frameworks provide custom operators (chapter 17) · The comparison with state flows assumes familiarity with state that has an initial value.
Concepts explained in this chapter
- flowA Flow is an asynchronous stream abstraction whose values are collected over time and whose producer may be cold, executing anew for each collection.
- hot flowA hot flow is a stream whose emissions occur independently of whether any collector is currently subscribed and whose emitted items can be shared among multiple collectors.
- shared flowA shared flow is a hot Kotlin Flow that broadcasts emissions to multiple collectors, with each collector receiving values only while subscribed unless replay is configured.
- state flowA StateFlow is a Kotlin coroutines hot-flow implementation for communicating and observing state, retaining a current state value for collectors.
- flow builderA flow builder is a Kotlin coroutines function that creates a cold Flow from a suspending block capable of emitting values.
- equality-based conflationEquality-based conflation suppresses a new stream value when it is equal to the value currently represented or most recently emitted.
- shareInshareIn is a kotlinx.coroutines Flow operation that converts a cold flow into a hot shared flow within a specified coroutine scope.
- MutableSharedFlowMutableSharedFlow is a kotlinx.coroutines type that represents a shared flow whose emissions and configuration can be changed by its producer.
- emitemit is the FlowCollector operation that publishes a value to a Flow collector and may suspend or respond to cancellation.
- stateInstateIn is a kotlinx.coroutines Flow operator that converts a flow into a state flow running in a specified coroutine scope.
- channel flowchannelFlow is a Kotlin coroutine builder that creates a cold flow supporting concurrent emissions from multiple coroutines.
- MutableStateFlowMutableStateFlow is a kotlinx.coroutines type that represents a state flow whose current value can be updated.
- collectcollect is a Kotlin Flow terminal operation that suspends while collecting all emitted elements and optionally processes each element with a lambda.
- suspension pointA suspension point is a point at which a suspending coroutine can pause and allow other coroutines sharing its dispatcher to execute.
- encapsulationInformation hiding is the design principle of concealing implementation details behind a restricted public interface.
- terminal operationA terminal operation is a sequence operation that produces a final result and triggers evaluation of the preceding transformations.
These guides are generated from the text of Manning books by Manning's knowledge graph and link to the sections they draw on. The full books are available on liveBook and at manning.com.
