One source, two independent readers
Requirement
One reading source must feed two independent computations — the total and the peak — while running exactly once. The source increments a counter each time it runs; print the total, the peak, and the counter to prove the single pass. The data is in the code; both versions must print the lines shown under Expected output.
Expected output
total: 74 peak: 25 source runs: 1
Side by side
RxDart
FxDart
Why they differ
Both models hit the same wall here: their sources restart per
consumer. Listening to a plain single-subscription stream twice is an
error; iterating a sync* generator twice quietly runs it
twice. And both libraries answer with the same idea — share one pass.
RxDart makes the stream connectable: publish()
defers the source, both reductions subscribe, and connect()
starts the single subscription that feeds them. FxDart's
tee keeps both reductions in step instead: each element
advances the total and the peak before the next one is pulled, so the
single pass never has to remember anything.
Both avoid a buffer, and for the same underlying reason — every
reader sees each element while it is the current one. That is what
connect() buys by making the readers attach first, and
what tee buys by taking the readers as folds: a seed
and a step, rather than two pipelines free to advance independently.
The constraint is the price. publish() will feed any
stream operators you care to subscribe; tee only feeds
folds. When the two readers really are independent pipelines, FxDart's
answer is fork — every fork of the same iterable object
is a cursor over one shared, buffered pass — and there the buffer
comes back, holding every value until the slowest cursor has consumed
it. So this is a tie on capability: the general tool costs memory on
both sides, and the specialised one is free on both. Pick the one
matching the model the rest of your code already lives in.
Benchmark
N = 100
Time Tie
Peak memory Tie
N = 1,000,000
Time FxDart wins
Peak memory FxDart wins
Bars are medians of repeated timed iterations in fresh processes per side (small N is batched for timer resolution). Sides within 5% of each other — or within 0.6 ms, a difference no person can perceive — count as a tie; close relative races are re-measured up to 5 runs. In an app, anything under a few milliseconds is invisible to the user regardless of which bar is shorter. Memory is peak process RSS. The Dart VM and the dataset are identical on both sides, so the difference between the two bars is what the pipeline itself holds onto.