settle method
The async analog of pumpAndSettle for data-bound widgets: yields to the
real event loop between steps so pending microtasks, timers, and stream
emissions land (a StreamBuilder/QueryBuilder's first value arrives
on a later event-loop turn), then flushes builds — repeating until a step
does no build work or timeout is hit.
This replaces the hand-rolled for (i in 0..N) { await Future.delayed(d); pump(); } loop every async test would otherwise need: synchronous pump
can't observe async emissions because it never turns the event loop.
Returns once the tree has been quiescent for stableSteps consecutive
steps. Requiring a short run of idle steps (rather than returning on the
first) guards the cold-start case: in-flight async work (a real DB query,
an HTTP call) that hasn't produced its first rebuild yet gets a window to
land and interrupt the idle streak — so settle() doesn't conclude
"done" before the data arrives. Tune up for slower backends.
Throws a StateError on timeout (the tree never settled) — the same
signal as Flutter's pumpAndSettle timeout.
Implementation
Future<void> settle({
Duration step = const Duration(milliseconds: 16),
Duration timeout = const Duration(seconds: 5),
int stableSteps = 4,
}) async {
_assertNotDisposed('settle');
assert(
step > Duration.zero,
'settle needs a positive step; a zero step never advances the clock '
'(nor elapsed), so timeout is never reached and the loop spins.',
);
var elapsed = Duration.zero;
var steps = 0;
var stable = 0;
var hasRendered = false;
while (elapsed < timeout) {
// Turn the real event loop so async completions (stream/Future) run
// their setState before we flush + test for quiescence.
await Future<void>.delayed(step);
final (quiescent, rendered) = _settleStep(step, hasRendered: hasRendered);
hasRendered = hasRendered || rendered;
stable = quiescent ? stable + 1 : 0;
if (stable >= stableSteps) {
_closeFrame();
return;
}
elapsed += step;
steps++;
}
throw StateError(
'settle() timed out after $timeout ($steps steps): the tree never '
'reached $stableSteps consecutive quiescent frames. A stream that never '
'stops emitting, or a continuous animation, will not settle — assert on '
'a bounded pump instead. ${_scheduler.activeTickerCount} ticker(s) are '
'active.',
);
}