Skip to content

[go_router] onExit fires spuriously for a pushed route when refreshListenable fires #189578

Open
flutter/packages
#12212

Description

@davidmigloz

What package does this bug report belong to?

go_router

What target platforms are you seeing this bug on?

Android, iOS, Web, macOS, Linux, Windows

Have you already upgraded your packages?

Yes

Dependency versions

pubspec.lock
go_router 17.3.0
(also reproduced against packages/go_router at flutter/packages main, 9e58c600b0)

Steps to reproduce

  1. Run the code sample below.
  2. Tap "Push form" (context.push of a route that defines onExit).
  3. Tap "Trigger refreshListenable" (stands in for any real-world tick: an auth refresh, a tab-refocus listener, etc.).

Expected results

Nothing happens — the pushed route never left the stack, so onExit must not run.

Actual results

The console prints onExit called on every refresh tick. For routes that use onExit to guard unsaved changes, the user gets a spurious "discard changes?" prompt every time refreshListenable fires while the route is on top.

Cause: ImperativeRouteMatch includes its push completer in operator==/hashCode, but the completer can't survive a codec round-trip — a refreshListenable re-parse (and state restoration) decodes a fresh Completer, so the re-decoded match list compares unequal to the live one even though nothing changed. That defeats setNewRoutePath's no-op fast path, and the router treats the re-parse as a real navigation change, running onExit against a route that is still on the stack.

Fix (see the linked PR): keep the completer in equality and instead have the parser carry over the live completer for each imperative match by pageKey (unique per push) when it re-decodes the current configuration (state restoration, browser history, refreshListenable re-parses). The re-decoded list then compares equal to the live one — completer included — so the no-op fast path holds and onExit doesn't fire; in-flight push futures are preserved, and imperative push/replace/pushReplacement are untouched. (Simply excluding the completer from equality is not a safe fix: same-location replace() deliberately reuses pageKey and differs only by its new completer, so its returned future would be orphaned.)

Code sample

Code sample
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

final ValueNotifier<int> refresh = ValueNotifier<int>(0);

void main() => runApp(App());

class App extends StatelessWidget {
  App({super.key});

  late final GoRouter router = GoRouter(
    refreshListenable: refresh,
    routes: [
      GoRoute(
        path: '/',
        builder: (_, __) => Scaffold(
          body: Center(
            child: Builder(
              builder: (context) => TextButton(
                onPressed: () => context.push('/form'),
                child: const Text('Push form'),
              ),
            ),
          ),
        ),
      ),
      GoRoute(
        path: '/form',
        onExit: (_, __) {
          debugPrint('onExit called');
          return true;
        },
        builder: (_, __) => Scaffold(
          body: Center(
            child: TextButton(
              onPressed: () => refresh.value++,
              child: const Text('Trigger refreshListenable'),
            ),
          ),
        ),
      ),
    ],
  );

  @override
  Widget build(BuildContext context) => MaterialApp.router(routerConfig: router);
}

Screenshots or Videos

Screenshots / Video demonstration

N/A (behavior visible in console output).

Logs

Logs
flutter: onExit called

(printed on each "Trigger refreshListenable" tap, although no navigation happened)

Flutter Doctor output

Doctor output
[✓] Flutter (Channel stable, 3.44.4, on macOS 26.5.2 25F84 darwin-arm64, locale en-US) [609ms]
    • Flutter version 3.44.4 on channel stable at ~/repos/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision ad70ec4617 (3 weeks ago), 2026-06-24 11:07:06 -0700
    • Engine revision a10d8ac38d
    • Dart version 3.12.2
    • DevTools version 2.57.0
    • Feature flags: enable-web, enable-linux-desktop, enable-macos-desktop, enable-windows-desktop, enable-android, enable-ios, cli-animations, enable-native-assets, enable-swift-package-manager, omit-legacy-version-file, enable-lldb-debugging, enable-uiscene-migration

[✓] Android toolchain - develop for Android devices (Android SDK version 36.0.0) [1,824ms]
    • Android SDK at ~/Library/Android/sdk
    • Emulator version unknown
    • Platform android-36, build-tools 36.0.0
    • ANDROID_HOME = ~/Library/Android/sdk
    • Java binary at: ~/Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java
      This is the JDK bundled with the latest Android Studio installation on this machine.
      To manually set the JDK path, use: `flutter config --jdk-dir="path/to/jdk"`.
    • Java version OpenJDK Runtime Environment (build 21.0.6+-13391695-b895.109)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 26.6) [1,335ms]
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 17F113
    • CocoaPods version 1.16.2

[✓] Chrome - develop for the web [6ms]
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Connected device (2 available) [6.8s]
    • macOS (desktop) • macos  • darwin-arm64   • macOS 26.5.2 25F84 darwin-arm64
    • Chrome (web)    • chrome • web-javascript • Google Chrome 150.0.7871.116

[✓] Network resources [789ms]
    • All expected network resources are available.

• No issues found!

Activity

  1. added
    frameworkflutter/packages/flutter repository. See also f: labels.
    f: routesNavigator, Router, and related APIs.
    packageflutter/packages repository. See also p: labels.
    has reproducible stepsThe issue has been confirmed reproducible and is ready to work on
    on Jul 16, 2026
  2. mbcorona commented on Jul 16, 2026

    @mbcorona
    Member

    Thanks for the report and linked PR.
    Routing to the framework team; PR flutter/packages#12212 is linked.

  3. added and removed
    frameworkflutter/packages/flutter repository. See also f: labels.
    on Aug 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work listf: routesNavigator, Router, and related APIs.has reproducible stepsThe issue has been confirmed reproducible and is ready to work onp: go_routerThe go_router packagepackageflutter/packages repository. See also p: labels.team-ecosystemOwned by Ecosystem teamtriaged-ecosystemTriaged by Ecosystem teamwaiting for PR to land (fixed)A fix is in flight

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions