In the previous article we explored Subjects in RxDart, including:
- PublishSubject
- BehaviorSubject
- ReplaySubject
Subjects act as entry points for reactive streams, but they are not the real power of Rx.
The real strength of reactive programming lies in operators.
Operators allow you to build data pipelines where streams can be transformed, combined, filtered, or controlled.
Conceptually:
Input Stream
↓
Operator
↓
Operator
↓
Operator
↓
Output Stream
Example:
queryStream
.debounceTime(Duration(milliseconds: 300))
.distinct()
.switchMap(api.search)
.listen(showResults);
This simple pipeline solves multiple problems:
- prevents excessive API calls
- avoids duplicate searches
- cancels outdated requests
- ensures only the latest results are shown
In this article we will cover 25 essential operators grouped into five categories.
1. Stream Transformation Operators
These operators transform data values.
1. map
Transforms each event.
Example:
Stream.fromIterable([1,2,3])
.map((v) => v * 2)
.listen(print);
Output:
2
4
6
Use case:
- converting API responses
- mapping DTO → domain model
Example in Flutter:
api.getUsers()
.map((dto) => User.fromDto(dto));
2. where
Filters events.
Stream.fromIterable([1,2,3,4])
.where((v) => v.isEven)
.listen(print);
Output:
2
4
Use case:
- filtering invalid input
- removing unwanted events
3. scan
Accumulates values (like reduce but continuous).
Example:
Stream.fromIterable([1,2,3])
.scan((acc, value, _) => acc + value, 0)
.listen(print);
Output:
1
3
6
Use case:
- cart totals
- running counters
- accumulating state
4. buffer
Collects events into batches.
Example:
stream.bufferCount(3)
Input:
1 2 3 4 5 6
Output:
[1,2,3]
[4,5,6]
Use case:
- batching analytics
- reducing API calls
5. distinct
Removes duplicate consecutive values.
Example:
Stream.fromIterable([1,1,2,2,3])
.distinct()
.listen(print);
Output:
1
2
3
Useful for preventing unnecessary UI updates.
2. Time-Based Operators
These control event timing.
6. debounceTime
Waits before emitting events.
Example:
queryStream.debounceTime(Duration(milliseconds: 300));
Typing:
A
Ap
App
Appl
Apple
Only emits:
Apple
Use case:
- search boxes
- typing events
7. throttleTime
Limits how frequently events emit.
Example:
scrollStream.throttleTime(Duration(milliseconds: 200));
Used for:
- scroll events
- drag gestures
8. delay
Delays event emission.
stream.delay(Duration(seconds: 1));
Useful for:
- UI animations
- loading indicators
9. interval
Forces a delay between events.
stream.interval(Duration(milliseconds: 100));
Use case:
- rate limiting
- controlled animations
10. timeout
Throws an error if no event occurs.
stream.timeout(Duration(seconds: 5));
Useful for:
- network request monitoring
3. Combination Operators
These combine multiple streams.
11. combineLatest
Emits when any stream updates, using latest values.
Example:
Rx.combineLatest2(
emailStream,
passwordStream,
(email, password) => email.isNotEmpty && password.length > 6,
);
Used for:
- form validation
12. zip
Pairs events in order.
Example:
Stream A: 1 2 3
Stream B: A B C
Output:
(1,A)
(2,B)
(3,C)
Useful when streams must stay synchronized.
13. merge
Merges multiple streams.
Rx.merge([
streamA,
streamB,
]);
Output contains events from both streams.
Use case:
- merging UI events
- combining notifications
14. concat
Processes streams sequentially.
Rx.concat([stream1, stream2]);
Stream2 starts only after Stream1 completes.
Useful for:
- sequential network tasks
15. withLatestFrom
Combines with the latest value of another stream.
Example:
buttonClick.withLatestFrom(userState, (click, user) => user);
Used for:
- sending requests with current state
4. Flattening Operators
These operators are critical for handling async tasks.
They transform:
Stream<Event> → Stream<Stream<Result>>
into:
Stream<Result>
16. switchMap
Cancels previous stream when a new event arrives.
Example:
queryStream.switchMap(api.search);
Scenario:
search: A
search: Ap
search: App
Only latest request survives.
Use case:
- search APIs
- autocomplete
17. flatMap (mergeMap)
Runs multiple streams concurrently.
Example:
stream.flatMap(api.request);
All requests continue simultaneously.
Use case:
- sending multiple independent API calls
18. concatMap
Processes async tasks sequentially.
Example:
stream.concatMap(api.request);
Ensures order.
Use case:
- file uploads
- ordered transactions
19. exhaustMap
Ignores new events while processing.
Example:
buttonClicks.exhaustMap(api.submit);
If the user clicks repeatedly:
click click click click
Only the first request executes.
Useful for:
- preventing duplicate submissions
20. asyncExpand
Expands each event into multiple events.
Example:
stream.asyncExpand((value) async* {
yield value;
yield value * 2;
});
5. Utility Operators
These help manage streams.
21. startWith
Adds an initial event.
stream.startWith(0);
Useful for:
- initial UI state
22. doOnData
Side-effect operator.
stream.doOnData((value) {
log(value);
});
Useful for debugging.
23. shareReplay
Shares and caches latest values.
stream.shareReplay(maxSize: 1);
Prevents multiple API calls when multiple listeners subscribe.
24. retry
Retries on failure.
stream.retry(3);
Useful for:
- unstable network requests
25. onErrorResume
Recovers from errors.
stream.onErrorResume((error, stack) {
return Stream.value([]);
});
Prevents stream termination.
Real Flutter Example: Reactive Search
Let’s combine multiple operators.
Requirements
User types
↓
debounce
↓
cancel previous request
↓
show results
Implementation:
class SearchBloc {
final _query = BehaviorSubject<String>();
Sink<String> get query => _query.sink;
late final Stream<List<Result>> results =
_query
.debounceTime(Duration(milliseconds: 300))
.distinct()
.switchMap(repository.search)
.shareReplay(maxSize: 1);
void dispose() {
_query.close();
}
}
This pipeline handles:
debounce typing
cancel outdated API calls
avoid duplicate requests
cache latest results
Real Flutter Example: Infinite Scroll
scroll position
↓
throttle
↓
load next page
↓
append items
Example:
scrollStream
.throttleTime(Duration(milliseconds: 200))
.where((pos) => pos > threshold)
.flatMap(repository.loadMore);
Operator Selection Guide
| Problem | Operator |
|---|---|
| Cancel old requests | switchMap |
| Run requests in parallel | flatMap |
| Sequential tasks | concatMap |
| Ignore duplicates | distinct |
| Debounce typing | debounceTime |
| Combine form inputs | combineLatest |
| Prevent spam clicking | exhaustMap |
Common Mistakes
Overusing Subjects
Bad:
Subject → Subject → Subject
Good:
Subject → Operators → Stream
Not understanding flattening operators
Using flatMap when switchMap is needed causes race conditions.
Ignoring stream lifecycle
Always close streams.
Key Takeaways
Operators are the heart of reactive programming in RxDart.
In this article we covered 25 essential operators, including:
- transformations
- timing controls
- stream combinations
- async flattening
- utility tools
Mastering these operators allows Flutter engineers to build powerful reactive pipelines.


