Chapter 17: Flow operators
This section introduces flow operators and distinguishes operators that build a modified flow from those that collect and execute it.
After this chapter you will be able to
- Intermediate and terminal flow operators. Distinguish intermediate from terminal flow operators by what they return and when the flow’s code executes.
- Upstream and downstream flows. Describe how an intermediate operator takes an upstream flow and returns a downstream flow, which can become the upstream flow for the next operator.
- Deferred intermediate operator behavior. Explain why calling an intermediate operator does not immediately execute its behavior, including when it is applied to a hot flow.
- Multiple emissions with transform. Distinguish transform from map by explaining that transform can emit arbitrarily many downstream elements for each upstream element, rather than one. Describe the example of emitting both uppercase and lowercase versions of each name.
- Take operators and flow cancellation. Explain that a take-family operator cancels the upstream flow when its condition is no longer valid, so no further elements are emitted. Describe take(5) as a controlled way to stop collection after five emissions.
- Flow lifecycle operator roles. Explain when onStart, onEach, onCompletion, and onEmpty run, including what can trigger onCompletion.
- Downstream emission visibility. Explain why moving onEmpty later in the operator chain changes whether an earlier onEach sees its emitted default element.
- Buffered flow execution. Explain why a cold-flow producer normally waits for downstream processing and how buffer lets it continue emitting, improving throughput.
- Conflation of intermediate values. Explain how conflate affects an upstream flow and a slow collector, and when discarding intermediate values is useful.
- Debounced search query flow. Explain why a search query flow might use debounce and what it emits: rather than starting a search on every keystroke, it passes a value downstream after the upstream flow has been quiet for the specified timeout.
- Upstream-only flowOn effect. Describe which parts of a flow pipeline flowOn affects and why it is called context preserving.
- Custom intermediate operator pattern. Explain how a custom intermediate operator collects upstream elements, processes them, and emits downstream elements while remaining cold. Illustrate the pattern with the section’s running-average example.
- Terminal operator execution. Explain that intermediate operators produce another flow without executing it, while terminal operators execute the upstream flow and may produce a value or collection, or simply trigger computations and side effects. Describe why terminal operators are suspending functions.
- Compose flow-to-state integration. Explain how Compose’s collectAsState converts a flow into a State object for use in a UI, and why an initial value is supplied.
What it builds on
- Executing sequence operations: Intermediate and terminal operations (chapter 6) · The analogous distinction between intermediate and terminal sequence operations.
- Flows model sequential streams of values (chapter 16) · Flows as streams of sequential values over time.
- Cold flows don’t do any work until collected (chapter 16) · Cold flows do not do work until collected.
- Hot flows (chapter 16) · The distinction between hot and cold flows.
- Cancelling the collection of a flow (chapter 16) · Cancelling flow collection through the coroutine scope associated with the collector
- Flows allow you to work with elements as they are emitted (chapter 16) · Flow elements can be processed as they are emitted.
- Using withContext to switch the dispatcher within a coroutine (chapter 14) · Switching coroutine dispatchers with withContext
- Creating a cold flow with the flow builder function (chapter 16) · Creating a cold flow with the flow builder.
Section by section
Introduction
17.1 Manipulating flows with flow operators
This section introduces flow operators and distinguishes operators that build a modified flow from those that collect and execute it.
- Intermediate and terminal flow operators: Distinguish intermediate from terminal flow operators by what they return and when the flow’s code executes.
Why it matters: The distinction tells you whether an operator merely prepares a flow or causes its work to run.
Key terms: intermediate flow operators, terminal flow operators, modified flow, collecting the flow
17.2 Intermediate operators are applied to an upstream flow and return a downstream flow
This section defines upstream and downstream flows in an operator chain and explains that intermediate flow operators defer their behavior until the flow is collected.
- Upstream and downstream flows: Describe how an intermediate operator takes an upstream flow and returns a downstream flow, which can become the upstream flow for the next operator.
Why it matters: These terms let you describe which part of an operator chain produces values for another part. - Deferred intermediate operator behavior: Explain why calling an intermediate operator does not immediately execute its behavior, including when it is applied to a hot flow.
Why it matters: It explains why adding an operator alone does not make its transformation run. - Familiar operators on flows: State that many intermediate operators familiar from sequences, including map, filter, and onEach, are also available for flows and operate on flow elements.
Why it matters: You can carry familiar sequence-operator ideas over to flows.
Key terms: intermediate operator, upstream flow, downstream flow, cold, hot flow, terminal operator, collect, map, filter, onEach
17.2.1 Emitting arbitrary values for each upstream element: The transform function
This subsection shows how transform can emit multiple downstream values for each upstream flow element and introduces flowOf as a shorthand for creating a flow from values.
- Multiple emissions with transform: Distinguish transform from map by explaining that transform can emit arbitrarily many downstream elements for each upstream element, rather than one. Describe the example of emitting both uppercase and lowercase versions of each name.
Why it matters: Transform is useful when one incoming value should produce more than one output value. - Flow creation with flowOf: State that flowOf is a shorthand builder for a flow that emits a supplied list of values, such as the names in the example.
Why it matters: It makes the starting flow in a short operator example easy to write.
Key terms: map, transform, upstream flow, downstream flow, flowOf, flow operators
17.2.2 The take operator family can cancel a flow
This subsection explains how take-family operators stop a flow by cancelling its upstream flow when their condition is no longer met.
- Take operators and flow cancellation: Explain that a take-family operator cancels the upstream flow when its condition is no longer valid, so no further elements are emitted. Describe take(5) as a controlled way to stop collection after five emissions.
Why it matters: You can limit a flow’s emissions without cancelling the collector’s coroutine scope yourself.
Key terms: take, takeWhile, upstream flow, cancelled
17.2.3 Hooking into flow phases with onStart, onEach, onCompletion, and onEmpty
This section explains how intermediate flow operators run logic at different lifecycle phases and why their position in a chain affects which emissions they receive.
- Flow lifecycle operator roles: Explain when onStart, onEach, onCompletion, and onEmpty run, including what can trigger onCompletion.
Why it matters: These hooks let you describe what should happen at each stage of collecting a flow. - Downstream emission visibility: Explain why moving onEmpty later in the operator chain changes whether an earlier onEach sees its emitted default element.
Why it matters: Operator order determines which hooks can observe values added to a flow.
Key terms: onStart, onEach, onCompletion, onEmpty, intermediate operators, upstream, downstream
17.2.4 Buffering elements for downstream operators and collectors: The buffer operator
This section explains how buffer lets a cold flow produce elements while downstream processing continues, and how to choose what happens when the buffer fills.
- Buffered flow execution: Explain why a cold-flow producer normally waits for downstream processing and how buffer lets it continue emitting, improving throughput.
Why it matters: Buffering can reduce the time spent waiting between production and slow downstream work. - Full buffer behavior: Describe the overflow choices available when a flow buffer reaches capacity.
Why it matters: A buffer’s capacity does not by itself determine what happens when production outpaces processing.
Key terms: cold flow, buffer operator, producer, collector, downstream flow, onBufferOverflow, SUSPEND, DROP_OLDEST, DROP_LATEST
17.2.5 Throwing away intermediate values: The conflate operator
This section explains how conflate lets a slow collector skip intermediate flow values while the producer continues emitting.
- Conflation of intermediate values: Explain how conflate affects an upstream flow and a slow collector, and when discarding intermediate values is useful.
Why it matters: Conflation is useful when fresh values matter more than processing every emitted value.
Key terms: conflate, upstream flow, collector, intermediate elements
17.2.6 Filtering out values on a timeout: The debounce operator
Explains how debounce delays flow values until input pauses, avoiding unnecessary search requests while a user types.
- Debounced search query flow: Explain why a search query flow might use debounce and what it emits: rather than starting a search on every keystroke, it passes a value downstream after the upstream flow has been quiet for the specified timeout.
Why it matters: Debouncing lets an instant-search interface respond when typing pauses without making a request for every keystroke.
Key terms: debounce operator, timeout, upstream, downstream flow, searchQuery flow
17.2.7 Switching the coroutine context on which a flow is executed: The flowOn operator
This subsection explains how flowOn changes the coroutine context for part of a flow pipeline without changing the downstream context.
- Flow execution context: Explain that a flow is collected in the context where collect is invoked by default, and that flowOn can run part of a processing pipeline in a different coroutine context or dispatcher.
Why it matters: It explains why you might introduce flowOn when different pipeline stages need different dispatchers. - Upstream-only flowOn effect: Describe which parts of a flow pipeline flowOn affects and why it is called context preserving.
Why it matters: The position of flowOn determines which processing stages switch context.
Key terms: coroutine context, collect, flowOn, dispatcher, upstream flow, downstream flow, context preserving
17.3 Creating custom intermediate operators
Shows how to build a custom intermediate flow operator by collecting an upstream flow inside a new, cold downstream flow.
- Custom intermediate operator pattern: Explain how a custom intermediate operator collects upstream elements, processes them, and emits downstream elements while remaining cold. Illustrate the pattern with the section’s running-average example.
Why it matters: This pattern lets you add flow behavior without making the upstream flow run before collection.
Key terms: intermediate operator, upstream flow, downstream flow, collect, flow builder, cold
17.4 Terminal operators execute the upstream flow and may compute a value
This section explains how terminal operators execute a flow, using collect and first as examples.
- Terminal operator execution: Explain that intermediate operators produce another flow without executing it, while terminal operators execute the upstream flow and may produce a value or collection, or simply trigger computations and side effects. Describe why terminal operators are suspending functions.
Why it matters: This distinction tells you when a flow’s specified work actually runs. - Collect callback shorthand: Explain that collect can take a lambda executed on each flow element, and that this is equivalent to calling onEach with that lambda followed by collect without parameters.
Why it matters: It connects the familiar collect callback to the intermediate-operator pipeline. - Collection and early cancellation: Describe how collect suspends until the whole flow is collected or its coroutine scope is cancelled, whereas first and firstOrNull can obtain one value and cancel the upstream flow after receiving an element.
Why it matters: The terminal operator you choose affects how long upstream execution continues.
Key terms: intermediate operators, terminal operators, upstream flow, suspending functions, collect, onEach, first, firstOrNull
17.4.1 Frameworks provide custom operators
This section shows how Compose converts a flow into UI state that can trigger recomposition.
- Compose flow-to-state integration: Explain how Compose’s collectAsState converts a flow into a State object for use in a UI, and why an initial value is supplied.
Why it matters: It connects values arriving from a flow to what the UI displays.
Key terms: Jetpack Compose, Compose Multiplatform, collectAsState, State, recomposition, initial value
17.5 Summary
Where this is used later
- Processing upstream exceptions with the catch operator (chapter 18) · The onEach operator in a flow pipeline
Concepts explained in this chapter
- intermediate operationAn intermediate operator is a Flow transformation that collects values from an upstream flow and emits transformed or otherwise produced values to a downstream flow.
- flowA Flow is an asynchronous stream abstraction whose values are collected over time and whose producer may be cold, executing anew for each collection.
- upstream flowAn upstream flow is the portion of a flow, including preceding intermediate operators, that occurs before a specified operator.
- terminal operationA terminal operation is a sequence operation that produces a final result and triggers evaluation of the preceding transformations.
- collectcollect is a Kotlin Flow terminal operation that suspends while collecting all emitted elements and optionally processes each element with a lambda.
- flow builderA flow builder is a Kotlin coroutines function that creates a cold Flow from a suspending block capable of emitting values.
- custom operatorA custom operator is a programmer-provided implementation of a predefined operator symbol.
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.
