Dart 40 ๐ฏ Asynchronous Data Streams: Stream Fundamentals and Creation
A Future delivers exactly one value โ or one error โ at one point in time, and then it is done. A Stream delivers a sequence of values over time. The difference is not just quantity. A future is a placeholder for a single asynchronous result; a stream is a channel for asynchronous events. A user typing into a field produces a stream of keystrokes. A file being read produces a stream of byte chunks. A WebSocket connection produces a stream of messages. Any operation that emits multiple values over time is a stream, and Dart’s Stream class is the abstraction that represents it.
The mental model is closest to a conveyor belt: values are placed on the belt one at a time, and they arrive at the consumer in order. A stream can emit any number of values, including zero. It can emit an error at any point, and after an error it can continue emitting values or close. When the stream is done, it emits a “done” event, and no further events follow. The consumer can listen to the stream, pause the subscription, resume it, or cancel it. The stream itself does not run until someone listens to it โ this is a critical property, and it is the difference between a stream that is a passive description of a sequence and a stream that is an active source of events.
Streams come in two fundamental varieties: single-subscription streams and broadcast streams. A single-subscription stream allows only one listener for its entire lifetime. The events are delivered in order to that one listener, and once the listener cancels, the stream is done. A broadcast stream allows any number of listeners, and events are delivered to all of them. The trade-off is buffering: a single-subscription stream buffers events until a listener attaches, so no data is lost; a broadcast stream does not buffer, so a listener that attaches late misses earlier events. The choice between them depends on whether the event source is inherently single-consumer or multi-consumer.
This chapter covers three areas. First, why streams exist โ the problem of representing sequences of asynchronous events, and how streams differ from futures. Second, how streams work โ the anatomy of a stream, the listen method, the subscription object, and the lifecycle of events. Third, how to create streams โ the Stream constructors, StreamController, async generators, and the transformation of existing sources into streams. The chapter ends with a complete example session, a quick reference, best practices, common pitfalls, real-world examples, and diagrams showing the stream lifecycle and the two subscription models.
Key point: A Stream is a sequence of asynchronous events. It can emit any number of values, zero or more errors, and a done event. A single-subscription stream allows one listener and buffers events until that listener attaches. A broadcast stream allows multiple listeners and does not buffer. A stream does nothing until it is listened to.
Why streams exist
The repeated-value problem. A Future represents one value. When an operation produces multiple values over time โ keystrokes, sensor readings, server messages, file chunks โ a future cannot represent it. A Future<List<int>> could represent a batch of values collected at one point, but it cannot represent values arriving incrementally. The consumer would have to wait for the entire batch before processing any of it, and there would be no way to react to the first value before the last one arrives. A Stream represents the sequence itself. Each value is delivered as it becomes available, and the consumer can process each one immediately .
The push-vs-pull problem. A future is pull-based in a specific sense: the consumer attaches a callback, and the value is pushed when ready. But the future itself represents a single completion. A stream is explicitly push-based: the source pushes events to the consumer as they occur, and the consumer reacts to each one. The consumer does not ask for the next value; the source decides when to emit. This matches the nature of event sources: a user presses a key when they press it, a server sends a message when it sends it, a sensor reads a value when it is read. The stream is the interface between a push-based source and a consumer that processes events as they arrive .
The backpressure problem. A push-based source can produce events faster than the consumer can process them. A stream addresses this with pause and resume. The subscription returned by listen has pause() and resume() methods. When the subscription is paused, the source buffers events (for a single-subscription stream) or drops them (for a broadcast stream). The consumer can pause when it is not ready to process and resume when it is. This is cooperative flow control: the source does not overwhelm the consumer, and the consumer controls the pace. Without this mechanism, a fast producer would flood a slow consumer, and events would be lost or memory would grow unbounded .
The single-vs-broadcast problem. Some event sources have one logical consumer. A file read produces bytes for whoever is reading the file. A single-subscription stream models this: one listener, events delivered in order, buffering until the listener attaches. Other sources have multiple logical consumers. A stream of user authentication changes might be needed by the navigation bar, the profile page, and the settings screen all at once. A broadcast stream models this: any number of listeners, events delivered to all, no buffering. The two models reflect two different realities, and the choice between them is a statement about how many consumers the source supports .
The lifecycle problem. A stream has a lifecycle: it can be listened to, paused, resumed, and canceled. It can emit values, errors, and a done event. The done event marks the end of the sequence, and after it, no further events are delivered. This lifecycle is what makes streams composable: they can be transformed with map, where, expand, and other operators, and the transformations preserve the lifecycle. A stream can be listened to once and transformed many times, and the resulting stream is itself a stream with the same lifecycle rules. This composability is what allows a stream-based architecture to describe complex event processing as a pipeline of simple transformations .
The trade-off. Streams introduce complexity that futures do not have. A future has one completion; a stream has many events, an error channel, and a done event. A single-subscription stream can only be listened to once, which is a common source of bugs when a widget rebuilds and tries to listen again. A broadcast stream drops events for listeners that attach late, which is a different kind of bug. The stream lifecycle requires careful management: subscriptions must be canceled to avoid leaks, and errors must be handled or they propagate to the zone error handler. The trade-off is between the power of representing sequences of asynchronous events and the discipline required to manage subscriptions, cancellation, and the single-vs-broadcast distinction.
a. The anatomy of a stream
A Stream<T> is a sequence of events of type T. Each event is either a data event (a value of type T), an error event, or a done event. The stream can emit any number of data events, zero or more error events, and exactly one done event. After the done event, no further events are emitted.
A stream is consumed with listen:
final subscription = stream.listen(
(value) => print('value: $value'),
onError: (error) => print('error: $error'),
onDone: () => print('done'),
);
The listen method returns a StreamSubscription. The subscription represents the active connection between the stream and the consumer. It has three lifecycle methods:
pause()โ stop delivering events. For a single-subscription stream, events are buffered. For a broadcast stream, events are dropped.resume()โ resume delivering events.cancel()โ stop listening. The subscription is closed, and no further events are delivered.cancel()returns a future that completes when the cancellation is processed.
The onError callback can take one argument (the error) or two (the error and the stack trace). If onError is omitted, errors are forwarded to the zone’s error handler, which in a Flutter app produces a red error screen in debug mode .
A stream does not run until it is listened to. A single-subscription stream is a description of a sequence; the sequence begins when the first listener attaches. This is why a stream can be created and stored without any side effects, and why the act of listening is what starts the work.
b. Single-subscription vs broadcast streams
A single-subscription stream allows exactly one listener for its entire lifetime. The first call to listen attaches the listener. A second call to listen throws a StateError. The stream buffers events until the listener attaches, so no data is lost if the listener is not yet ready. The events are delivered in order to that one listener .
final stream = Stream.fromIterable([1, 2, 3]);
stream.listen((v) => print('first: $v'));
// stream.listen((v) => print('second: $v'));
// ^ throws: Stream has already been listened to.
The buffering behavior means that a single-subscription stream can be created, and its events can be produced, before any listener attaches. The events are queued and delivered when listen is called. This is useful when the stream represents a resource that should be read from the beginning, such as a file or an HTTP response body.
A broadcast stream allows any number of listeners. Each listener receives events from the point at which it attaches. Events emitted before a listener attaches are not delivered to that listener โ there is no buffering. This is the correct behavior for an event source that represents “what is happening now,” such as a user input stream or a WebSocket message stream .
final controller = StreamController<int>.broadcast();
controller.stream.listen((v) => print('listener 1: $v'));
controller.stream.listen((v) => print('listener 2: $v'));
controller.add(1);
// listener 1: 1
// listener 2: 1
A single-subscription stream can be converted to a broadcast stream with asBroadcastStream(). This returns a broadcast stream that shares the original stream’s events. The original stream is listened to once, and the broadcast stream delivers the events to all its listeners. The conversion is one-way: a broadcast stream cannot be converted back to a single-subscription stream.
c. Creating streams
Streams can be created in several ways, each suited to a different source.
From a collection. Stream.fromIterable converts an iterable into a stream. The values are emitted one at a time, asynchronously:
final stream = Stream.fromIterable([1, 2, 3]);
From a future. Stream.fromFuture creates a stream that emits one value when the future completes, then closes:
final stream = Stream.fromFuture(fetchData());
From a periodic timer. Stream.periodic creates a stream that emits values at a fixed interval. The values are computed by a callback that receives the event index:
final stream = Stream.periodic(
Duration(seconds: 1),
(count) => count,
);
From a value. Stream.value creates a stream that emits one value and closes:
final stream = Stream.value(42);
From an empty stream. Stream.empty creates a stream that emits no values and closes immediately:
final stream = Stream.empty();
From an error. Stream.error creates a stream that emits one error and closes:
final stream = Stream.error(Exception('boom'));
From a controller. StreamController is the most flexible way to create a stream. It exposes a stream property and an add method. Events added to the controller are delivered to the stream’s listeners:
final controller = StreamController<int>();
controller.stream.listen((v) => print(v));
controller.add(1);
controller.add(2);
controller.addError(Exception('boom'));
controller.close();
The controller has a stream property that is the single-subscription stream. A broadcast controller is created with StreamController.broadcast() and its stream is a broadcast stream. The controller must be closed with close() when no more events will be added. The onListen, onPause, onResume, and onCancel callbacks can be provided to react to the subscription lifecycle.
From an async generator. An async* function returns a stream. The yield keyword emits a value, and yield* delegates to another stream:
Stream<int> countTo(int n) async* {
for (var i = 1; i <= n; i++) {
yield i;
}
}
The function body runs when the stream is listened to. Each yield emits a value. The function can await between yields, and the stream will pause until the await completes. The yield* form delegates to another stream, emitting all of its events .
Complete Example Session
// ============================================
// PART 1: A BASIC STREAM
// ============================================
// Stream.fromIterable creates a stream that
// emits each value in the iterable.
void main() {
final stream = Stream.fromIterable([1, 2, 3]);
stream.listen(
(value) => print('value: $value'),
onError: (error) => print('error: $error'),
onDone: () => print('done'),
);
}
// Output:
// value: 1
// value: 2
// value: 3
// done
// ============================================
// PART 2: THE SUBSCRIPTION LIFECYCLE
// ============================================
// listen returns a subscription that can be
// paused, resumed, and canceled.
final subscription = Stream.periodic(
Duration(seconds: 1),
(count) => count,
).listen((value) => print('tick: $value'));
// Later:
subscription.pause(); // stop delivering events
subscription.resume(); // resume delivery
subscription.cancel(); // stop listening permanently
// ============================================
// PART 3: SINGLE-SUBSCRIPTION RESTRICTION
// ============================================
// A single-subscription stream can only be
// listened to once.
final single = Stream.fromIterable([1, 2, 3]);
single.listen((v) => print('first: $v'));
// single.listen((v) => print('second: $v'));
// ^ throws StateError: Stream has already been listened to.
// ============================================
// PART 4: BROADCAST STREAM
// ============================================
// A broadcast stream allows multiple listeners.
// Events are delivered to all of them.
final controller = StreamController<int>.broadcast();
controller.stream.listen((v) => print('listener 1: $v'));
controller.stream.listen((v) => print('listener 2: $v'));
controller.add(1);
controller.add(2);
controller.close();
// Output:
// listener 1: 1
// listener 2: 1
// listener 1: 2
// listener 2: 2
// ============================================
// PART 5: SINGLE-SUBSCRIPTION STREAM CONTROLLER
// ============================================
// A single-subscription controller buffers
// events until a listener attaches.
final singleController = StreamController<int>();
singleController.add(1); // buffered
singleController.add(2); // buffered
singleController.stream.listen((v) => print('buffered: $v'));
// Output:
// buffered: 1
// buffered: 2
// ============================================
// PART 6: ASYNC GENERATOR
// ============================================
// An async* function returns a stream. Each
// yield emits a value.
Stream<int> countTo(int n) async* {
for (var i = 1; i <= n; i++) {
await Future.delayed(Duration(milliseconds: 100));
yield i;
}
}
void main() async {
await for (final value in countTo(3)) {
print('count: $value');
}
}
// Output (100ms apart):
// count: 1
// count: 2
// count: 3
// ============================================
// PART 7: yield* โ DELEGATING TO ANOTHER STREAM
// ============================================
Stream<int> inner() async* {
yield 1;
yield 2;
}
Stream<int> outer() async* {
yield 0;
yield* inner(); // emits 1, 2
yield 3;
}
// Output: 0, 1, 2, 3
// ============================================
// PART 8: ERROR EVENTS
// ============================================
// A stream can emit errors. The onError callback
// receives them.
final errorController = StreamController<int>();
errorController.stream.listen(
(v) => print('value: $v'),
onError: (e) => print('error: $e'),
onDone: () => print('done'),
);
errorController.add(1);
errorController.addError(Exception('boom'));
errorController.add(2);
errorController.close();
// Output:
// value: 1
// error: Exception: boom
// value: 2
// done
// ============================================
// PART 9: STREAM TRANSFORMATIONS
// ============================================
// Streams can be transformed with map, where,
// and other operators.
Stream.fromIterable([1, 2, 3, 4, 5])
.where((n) => n.isEven)
.map((n) => n * 10)
.listen((v) => print(v));
// Output:
// 20
// 40
// ============================================
// PART 10: CANCELING A SUBSCRIPTION
// ============================================
// cancel() stops the subscription. For a
// controller-backed stream, it triggers onCancel.
final cancelController = StreamController<int>(
onCancel: () => print('subscription canceled'),
);
final sub = cancelController.stream.listen((v) => print(v));
cancelController.add(1);
// Later:
sub.cancel(); // prints 'subscription canceled'
The ten parts show basic stream listening, the subscription lifecycle, the single-subscription restriction, broadcast streams, buffering, async generators, yield*, error events, transformations, and cancellation.
Quick Reference
Stream Event Types
| Event | Meaning |
|---|---|
| Data | A value of type T |
| Error | An error and optional stack trace |
| Done | The stream has closed; no further events |
listen Parameters
| Parameter | Purpose |
|---|---|
onData | Called for each data event |
onError | Called for each error event |
onDone | Called when the stream closes |
cancelOnError | If true, cancels the subscription on the first error |
Subscription Methods
| Method | Effect |
|---|---|
pause() | Stop delivering events (buffered or dropped) |
resume() | Resume delivering events |
cancel() | Stop listening permanently |
Single-Subscription vs Broadcast
| Aspect | Single-subscription | Broadcast |
|---|---|---|
| Listeners | One | Many |
| Buffering | Yes, until listener attaches | No |
| Late listeners | Receive all buffered events | Miss earlier events |
| Conversion | asBroadcastStream() | Cannot convert back |
Stream Constructors
| Constructor | Purpose |
|---|---|
Stream.fromIterable | Emit each item in an iterable |
Stream.fromFuture | Emit one value from a future |
Stream.periodic | Emit values at a fixed interval |
Stream.value | Emit one value and close |
Stream.empty | Emit nothing and close |
Stream.error | Emit one error and close |
StreamController | Full control over event emission |
async* function | Emit values with yield |
Best Practices
โ Do This:
// Cancel subscriptions when done
final sub = stream.listen((v) => print(v));
sub.cancel(); // โ
// Use broadcast for multi-consumer streams
final controller = StreamController<int>.broadcast(); // โ
// Handle errors
stream.listen((v) => print(v), onError: (e) => print(e)); // โ
// Close controllers
controller.close(); // โ
// Use async* for generated sequences
Stream<int> count() async* { yield 1; yield 2; } // โ
// Use await for to consume streams
await for (final v in stream) { print(v); } // โ
โ Don’t Do This:
// Don't listen to a single-subscription stream twice
stream.listen((v) => print(v));
stream.listen((v) => print(v)); // throws // โ
// Don't forget to close a controller
controller.add(1); // controller never closed // โ
// Don't ignore errors
stream.listen((v) => print(v)); // no onError // โ
// Don't use a broadcast stream for one-shot data
final broadcast = Stream.fromIterable([1]).asBroadcastStream(); // โ
// Don't leak subscriptions
stream.listen((v) => print(v)); // no cancel // โ
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Stream already listened to | Single-subscription stream | Use broadcast or asBroadcastStream |
| Late listener misses events | Broadcast stream does not buffer | Use single-subscription or attach early |
| Subscription leak | cancel() not called | Cancel in dispose or when done |
| Controller not closed | close() not called | Close when no more events |
| Error unhandled | No onError | Add onError or catchError |
| Async generator not running | Not listened to | Streams are lazy; listen to start |
| Events out of order | Multiple async sources | Single stream preserves order |
Real-World Examples
1. From Iterable
Stream.fromIterable([1, 2, 3]).listen(print);
2. From Future
Stream.fromFuture(fetchData()).listen(print);
3. Periodic Timer
Stream.periodic(Duration(seconds: 1), (i) => i).listen(print);
4. Single Value
Stream.value(42).listen(print);
5. Error Stream
Stream.error(Exception('boom')).listen(print, onError: print);
6. StreamController
final c = StreamController<int>();
c.stream.listen(print);
c.add(1);
c.close();
7. Broadcast Controller
final c = StreamController<int>.broadcast();
8. Async Generator
Stream<int> gen() async* { yield 1; yield 2; }
9. yield*
Stream<int> outer() async* { yield 0; yield* inner(); }
10. Transform
stream.where((n) => n > 0).map((n) => n * 2).listen(print);
Visual
Stream Lifecycle
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ STREAM LIFECYCLE โ
โ โ
โ Stream created โ
โ โ โ
โ โ (no events until listen) โ
โ โผ โ
โ listen() called โ
โ โ โ
โ โโโโบ data event โโโบ onData โ
โ โโโโบ data event โโโบ onData โ
โ โโโโบ error event โโบ onError โ
โ โโโโบ data event โโโบ onData โ
โ โ โ
โ โผ โ
โ close() called โ
โ โ โ
โ โผ โ
โ done event โโโบ onDone โ
โ โ
โ Subscription methods: โ
โ pause() โโโบ events buffered (single) or dropped (broadcast)
โ resume() โโโบ events delivered again โ
โ cancel() โโโบ subscription closed, no more events โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Single-Subscription vs Broadcast
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ SINGLE-SUBSCRIPTION โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Stream โโโบ buffers โโโบ listener โ โ
โ โ โ โ
โ โ Only one listener. Events buffered until it โ โ
โ โ attaches. No events lost. โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ BROADCAST โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Stream โโโฌโโโบ listener 1 โ โ
โ โ โโโโบ listener 2 โ โ
โ โ โโโโบ listener 3 โ โ
โ โ โ โ
โ โ Many listeners. No buffering. Late listeners โ โ
โ โ miss earlier events. โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ Use single-subscription for one-shot data (file, HTTP). โ
โ Use broadcast for ongoing events (input, WebSocket). โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Event Ordering
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ EVENT ORDERING โ
โ โ
โ controller.add(1); โ
โ controller.addError(Exception('boom')); โ
โ controller.add(2); โ
โ controller.close(); โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ onData: 1 โ โ
โ โ onError: Exception: boom โ โ
โ โ onData: 2 โ โ
โ โ onDone: (stream closed) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ Errors do not close the stream. The stream continues โ
โ emitting events after an error until close() is called. โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Async Generator
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ASYNC GENERATOR โ
โ โ
โ Stream<int> countTo(int n) async* { โ
โ for (var i = 1; i <= n; i++) { โ
โ await Future.delayed(Duration(seconds: 1)); โ
โ yield i; โ
โ } โ
โ } โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ listen() called โ โ
โ โ โ โ โ
โ โ โผ โ โ
โ โ loop starts โ โ
โ โ โ โ โ
โ โ โโโ i=1: await, yield 1 โ โ
โ โ โโโ i=2: await, yield 2 โ โ
โ โ โโโ i=3: await, yield 3 โ โ
โ โ โ โ โ
โ โ โผ โ โ
โ โ function ends, stream closes โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ The function body runs when listened to. Each yield โ
โ emits a value. The stream pauses at each await. โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
Stream<T> | Sequence of asynchronous events of type T |
| Event types | Data, error, done |
listen | Attaches a listener; returns a StreamSubscription |
pause/resume/cancel | Subscription lifecycle methods |
| Single-subscription | One listener; buffers events |
| Broadcast | Many listeners; no buffering |
asBroadcastStream | Converts single-subscription to broadcast |
StreamController | Full control over event emission |
async* / yield | Generator that returns a stream |
| Lazy | Streams do nothing until listened to |
Key takeaways:
- A stream is a sequence of asynchronous events. Unlike a future, which delivers one value, a stream can deliver any number of values, zero or more errors, and a done event .
- A stream does nothing until it is listened to. The act of calling
listenstarts the sequence. This is why streams can be created and stored without side effects. - Single-subscription streams allow one listener and buffer events. The first
listenattaches the listener. A secondlistenthrows. Events emitted before the listener attaches are buffered and delivered in order . - Broadcast streams allow many listeners and do not buffer. Each listener receives events from the point it attaches. Events emitted before a listener attaches are missed. Use broadcast for ongoing event sources with multiple consumers .
StreamControlleris the most flexible way to create a stream. It exposes astreamproperty andadd,addError, andclosemethods. TheonListen,onPause,onResume, andonCancelcallbacks react to the subscription lifecycle.async*functions return streams. Theyieldkeyword emits a value, and the function body runs when the stream is listened to. Theyield*form delegates to another stream, emitting all of its events .- Errors do not close a stream. An error event is delivered to the
onErrorcallback, and the stream continues emitting data events untilclose()is called. This allows a stream to survive errors and continue . - Cancel subscriptions to avoid leaks. A subscription that is not canceled keeps the stream and its resources alive. In a widget-based framework, subscriptions should be canceled when the widget is disposed.
Remember: A Stream is the abstraction for a sequence of asynchronous events. It can emit any number of values, zero or more errors, and a done event. The two varieties โ single-subscription and broadcast โ model two different realities: one consumer with buffering, or many consumers without. The StreamController and async* generators are the primary ways to create streams, and Stream.fromIterable, Stream.periodic, and the other constructors cover the common cases. A stream is lazy: it does nothing until listened to, and the subscription controls the flow with pause, resume, and cancel. Understanding these fundamentals is the prerequisite for transforming streams, combining them, and using them in a reactive architecture.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!