uniqAdjacent

Drops elements equal to their predecessor — only adjacent duplicates go, and no seen-set builds up.

Iterable<A> uniqAdjacent<A>(Iterable<A> iterable) Iterable<A> uniqAdjacentBy<A, B>(B Function(A a) f, Iterable<A> iterable) FxAsyncIterable<A> uniqAdjacentAsync<A>(FxAsyncIterable<A> iterable) FxAsyncIterable<A> uniqAdjacentByAsync<A, B>(FutureOr<B> Function(A a) f, FxAsyncIterable<A> iterable) Fx<T> Fx<T>.uniqAdjacent() / .uniqAdjacentBy<B>(B Function(T a) f) // chain (sync) FxAsync<T> FxAsync<T>.uniqAdjacent() / .uniqAdjacentBy<B>(FutureOr<B> Function(T a) f) // chain (async) FxEvents<T> FxEvents<T>.uniqAdjacent() / .uniqAdjacentBy<B>(B Function(T a) f) // chain (events)

Lecture

uniq answers "have I ever seen this value?" — it keeps a set of everything seen so far. uniqAdjacent() answers a different question: "did the value change?" It compares each element only with its immediate predecessor, so [1, 1, 2, 2, 1] becomes (1, 2, 1) — the trailing 1 survives, because it is a new run, not a repeat of the current one.

That makes it the operator for collapsing runs: status feeds that re-report the same state every tick, sensor values that plateau, log streams that repeat a level. And because there is no seen-set, memory stays constant no matter how long the sequence — safe on endless sources where uniq would grow forever. uniqAdjacentBy(key) compares by a derived key, mirroring uniqBy.

fxdart extension (no FxTS counterpart) — Rx calls it distinctUntilChanged, Dart streams call it Stream.distinct. The async key callback runs one element at a time (the comparison is inherently ordered), but the upstream still evaluates in parallel under concurrent.

Demo 1 · Collapse runs, keep returns

Demo 2 · State changes by key

Try it yourself

Exercise: report only the moments a sensor's zone changes.

Related: uniq — global dedup with a seen-set · uniqBy — global dedup by key · pairwise — when you need both sides of the change