In Dart, what does `asBroadcastStream()` do with the underlying subscription as listeners come and go, and when do its `onListen` and `onCancel` callbacks matter?
answer
- subscribes on the first listener
- stays subscribed with zero listeners
- events with no listener are lost
- onCancel: pause or cancel the source
- cancelling there ends it for everyone
basics
~20 sasBroadcastStream subscribes to the source when its first listener arrives and stays subscribed until the source ends or a callback cancels it, even with no listeners, so events fired meanwhile are lost. onListen and onCancel let you pause, resume or cancel the source.
solid answer
~40 s`asBroadcastStream()` returns a broadcast stream that **subscribes to the source when its first listener arrives** and, by default, **stays subscribed until the source ends**, even after every listener has cancelled. So the source keeps running, and events it produces while nobody is listening are dropped; a later listener sees only what comes next. The optional `onCancel` callback runs when the last listener leaves and receives a subscription-like handle to the source, so you can `pause()` it to stop losing events, or `cancel()` it to free the resource. `onListen` runs again when a listener returns, and is where you `resume()`. Cancel there only when nobody is listening: cancelling the source stops all current subscribers without even a done event.
go deeper
Know that asBroadcastStream lets several listeners share a stream that would otherwise allow only one.
Explain that it subscribes on the first listener, stays subscribed by default, and that events fired with no listeners are lost.
Use onCancel and onListen to pause, resume or cancel the source deliberately, and avoid the cancel-while-listening trap that silences current subscribers.
Decide where sharing belongs: adapting at call sites with asBroadcastStream, or making the owning layer expose broadcast streams with a documented lifecycle.
## What the adapter is for A single-subscription stream, such as one returned by an `async*` function, accepts one listener. When several parts of an app need the same events, for example a temperature display and a logger reading one sensor, `asBroadcastStream()` wraps it in a **broadcast stream** that many listeners can share, while the source itself still has exactly one subscriber: the adapter. ## The default lifecycle The SDK documents the behaviour precisely: 1. The adapter **subscribes to the source when its first listener is added**. Creating it does nothing on its own. 2. It then **stays subscribed until the source ends**, or until one of the callbacks cancels that subscription. 3. Listeners can come and go freely; each sees the events fired while it is subscribed. Point 2 is the surprise. With the defaults, when every listener cancels, the adapter keeps the source subscription open. The sensor keeps being polled, and because a broadcast stream fires events **whether there are listeners or not**, those readings are **lost**. When a listener returns, it picks up from the next reading. For a sensor that is often fine; for a file or a network body it wastes the resource and loses data. ## Three states to reason about It helps to think of the adapter as moving between three states: 1. **Never listened to**: the source is not subscribed; the sensor generator has not started. 2. **One or more listeners**: the source is subscribed and every event is delivered to the current listeners. 3. **All listeners gone**: with the defaults, the source is **still subscribed**, and each event is fired to nobody and lost. State 3 is where the callbacks come in. The adapter never returns to state 1 on its own; only the source ending, or a callback cancelling it, releases the underlying subscription. ## Controlling it with `onListen` and `onCancel` The signature is `Stream<T> asBroadcastStream({void onListen(StreamSubscription<T> subscription)?, void onCancel(StreamSubscription<T> subscription)?})`: - **`onCancel`** is called when the broadcast stream **stops having listeners**. - **`onListen`** is called when it gets a listener, including when a listener arrives again after `onCancel` ran. - Both receive a **subscription-like object for the underlying source subscription**. You can `pause`, `resume` or `cancel` it inside the callback, but you cannot replace its event handlers or call `asFuture` on it. Two common policies: | Goal | `onCancel` | `onListen` | |---|---|---| | Do not lose events while nobody listens | `sub.pause()` | `if (sub.isPaused) sub.resume()` | | Free the source when nobody listens | `sub.cancel()` | nothing: the source cannot restart | Pausing works because a paused subscription **buffers** events it receives, and a non-broadcast source is usually told to stop producing; an `async*` source pauses at its next `yield`. The cost of pausing is memory if the source keeps pushing regardless. ## The cancel trap The SDK warns that cancelling is intended for when there are **no current subscribers**. If the source subscription passed to a callback is cancelled, **no further events are ever emitted** by current subscriptions on the broadcast stream, **not even a done event**. Code awaiting `broadcast.first` or an `await for` loop over it would simply wait forever. So cancel only from `onCancel`, never from `onListen` while others are listening. ## Example ```dart final shared = readings().asBroadcastStream( onCancel: (sub) => sub.pause(), onListen: (sub) { if (sub.isPaused) sub.resume(); }, ); final display = shared.listen((t) => print('display: $t')); final logger = shared.listen((t) => print('log: $t')); // later await display.cancel(); await logger.cancel(); // last listener gone: onCancel pauses the sensor ``` ## Choosing between this and alternatives - If each consumer can have its own source, calling the producer twice avoids sharing entirely. - If the source's owner can expose a broadcast stream directly, that is cleaner than adapting at every call site. - If late listeners must see the **latest value** on subscription, a plain broadcast stream will not do that; it needs a replaying design built for it. `asBroadcastStream` is the right tool when you have a single-subscription stream you do not own and several consumers that are happy to see only what happens while they listen.
- Why might the sensor keep being polled after both listeners cancel?Because by default the adapter stays subscribed to the source until the source ends. With no `onCancel` callback, cancelling every listener leaves the underlying subscription active, so the `async*` generator keeps running and its readings are dropped. Pass `onCancel: (sub) => sub.pause()` or `sub.cancel()` to change that.
- What happens to current listeners if `onListen` cancels the source subscription?They stop receiving anything. The SDK documents that cancelling the subscription handed to `onListen` or `onCancel` means no further events are emitted on current subscriptions, not even a done event, so an `await for` over the broadcast stream would wait forever. Cancel only when there are no listeners.
saying these in an interview costs you the question
- asBroadcastStream cancels the source when the last listener leaves
- asBroadcastStream subscribes to the source as soon as it is called
- Events are buffered for listeners who join later
- Cancelling the source in a callback sends done to current listeners
- The callbacks can replace the source subscription's event handlers