Skip to content

Flutter runtime errorFlutter: “Failed assertion: ‘!_debugLocked’: is not true” (Navigator)

Navigating throws Failed assertion: line … pos …: ‘!_debugLocked’: is not true, from navigator.dart. The Navigator was already in the middle of a push, pop or page update, and code asked it to navigate again. Typical triggers are a pop inside a popUntil predicate, a dialog’s .then() that pops again while the dialog’s own pop is running, a pop from dispose() or a pop handler, a double tap that fires two pops, and a redirect from build().

Short answer

Run one navigation at a time. Replace a pop followed by a push with one call, such as pushReplacement or pushAndRemoveUntil; await a dialog before navigating again; and never navigate from dispose(), a popUntil predicate or build(). When the navigation reacts to state (a login result, a stream event), schedule it after the frame with WidgetsBinding.instance.addPostFrameCallback.

Why the Navigator is locked, and the fix

  1. 01

    A second navigation while the first is still running

    A pop inside a popUntil predicate, a showDialog(...).then() that pops again while the dialog’s own pop is running, a pop from dispose() or a pop handler, a pop followed straight away by a push, or a double tap on a back button all start a second operation before the first has finished. Use the single method that does both, or await the first one.

    // instead of pop() then push()
    Navigator.of(context).pushReplacement(
      MaterialPageRoute(builder: (_) => const ResultPage()),
    );
    
    // back to the first screen and open another
    Navigator.of(context).pushAndRemoveUntil(
      MaterialPageRoute(builder: (_) => const HomePage()),
      (route) => false,
    );
    
    // a dialog: await it, then navigate once
    final ok = await showDialog<bool>(
      context: context,
      builder: (_) => const ConfirmDialog(),
    );
    if (ok == true && context.mounted) Navigator.of(context).pop();
  2. 02

    Navigator called from build()

    “Schedule a callback for the end of this frame.” Flutter, SchedulerBinding.addPostFrameCallback

    Redirecting from build() (for example when a FutureBuilder or BlocBuilder sees the user is logged in) navigates in the middle of a frame, and fails when the Navigator itself is rebuilding. Do it after the frame instead, and only once.

    if (state.isLoggedIn && !_redirected) {
      _redirected = true;
      WidgetsBinding.instance.addPostFrameCallback((_) {
        if (!context.mounted) return;
        Navigator.of(context).pushReplacementNamed('/home');
      });
    }
  3. 03

    A listener navigates while the Navigator is popping

    “This method does not request a new frame.” Flutter, SchedulerBinding.addPostFrameCallback

    A stream, ChangeNotifier or WillPopScope/PopScope handler that pushes while a pop is still running hits the same lock. Defer the navigation to after the frame. addPostFrameCallback does not request a frame, so when the event can arrive while the app is idle, call ensureVisualUpdate too.

    authStream.listen((user) {
      WidgetsBinding.instance.addPostFrameCallback((_) {
        navigatorKey.currentState?.pushNamedAndRemoveUntil('/login', (_) => false);
      });
      WidgetsBinding.instance.ensureVisualUpdate();
    });

Checked against the vendor’s docs on : Flutter API: SchedulerBinding.addPostFrameCallback.

Rather hand it over?

Fixed scope, fixed price, paid upfront. The work happens in writing; no call needed.

Related: Flutter “Navigator operation requested with a context” · Flutter setState() called during build · Flutter “deactivated widget’s ancestor” · Flutter “type 'Null' is not a subtype of type 'String'”

Questions

More on this error

Does Future.microtask or Future.delayed(Duration.zero) work too?

Often, because it moves the call out of the current build. addPostFrameCallback is clearer about when it runs, so prefer it for navigation that reacts to state.

I use go_router. Can this still happen?

Yes, if context.go runs inside build. Use go_router’s redirect for auth-based routing instead of navigating from build.

Can you fix it for me?

If one bug is the whole problem, the App Fix Ticket covers it: a written diagnosis first, then the fix. If the app has more going on, the Launch Check is a written check of the whole app. Each issue found is rated critical, high, medium or low, with where it is and how it was confirmed. No issues worth fixing, no charge. If the Launch Check finds nothing rated medium or higher, Simon refunds the full $450. You keep the report either way. This applies to the first 10 Launch Checks bought after October 30, 2026.

A different error?

Describe it and get a written quote

Send the error and what you were doing. You get a written scope, a fixed price and a timeline; Simon replies by email, usually within one business day.