pumpAndSettle method

void pumpAndSettle({
  1. Duration step = const Duration(milliseconds: 16),
  2. Duration timeout = const Duration(seconds: 10),
})

Pumps in step increments until a frame does no build work (the tree has settled) or timeout elapses. Throws when timeout is hit while the tree still rebuilds every step.

Mirrors WidgetTester.pumpAndSettle, and like it keys off "is another frame's worth of work pending" (here: did flushBuild rebuild anything) rather than "are any tickers registered" — so a focused TextInput's perpetual cursor-blink ticker no longer hangs settle; it returns in the gaps between blinks. A genuinely continuous animation (one that rebuilds on every step) is what trips the timeout — pump a bounded duration for those instead of settling.

NOTE: synchronous, so it cannot observe async work (stream/Future emissions). For data-bound widgets (StreamBuilder/QueryBuilder) use the async settle.

Implementation

void pumpAndSettle({
  Duration step = const Duration(milliseconds: 16),
  Duration timeout = const Duration(seconds: 10),
}) {
  _assertNotDisposed('pumpAndSettle');
  assert(
    step > Duration.zero,
    'pumpAndSettle needs a positive step; a zero step never advances the '
    'clock, so timeout is never reached.',
  );
  var elapsed = Duration.zero;
  var hasRendered = false;
  while (elapsed < timeout) {
    final (quiescent, rendered) = _settleStep(step, hasRendered: hasRendered);
    hasRendered = hasRendered || rendered;
    if (quiescent) {
      _closeFrame();
      return;
    }
    elapsed += step;
  }
  throw StateError(
    'pumpAndSettle timed out after $timeout: the tree never reached a '
    'quiescent frame — something rebuilds on every step. '
    '${_scheduler.activeTickerCount} ticker(s) are active; if a continuous '
    'animation is intentional, pump a bounded duration instead of settling. '
    '(A perpetual-but-idle ticker such as a cursor blink does NOT cause '
    'this — settle returns in the gaps between its frames.)',
  );
}