Skip to content

[go_router] ShellRoutes break iOS swipe back navigation #120353

Closed
flutter/packages
#9968

Description

@mnefzger

go_router version: 6.0.4
flutter version: 3.3.9

Hi go_router team,
we experience an unexpected behaviour when it comes to using GoRouter with ShellRoutes in combination with the iOS "swipe to go back" gesture. The navigation seems to skip over all routes wrapped in the ShellRoute. Everything works as expected with the Android "swipe to go back" gesture.

Steps to Reproduce

With the below code sample using a ShellRoute, navigate to sub-page "AcknowledgementsPage" and then "swipe to go back" on iOS.

Expected results: Navigation goes back only one page to "AboutThisAppPage".

Actual results: Navigation skips any page wrapped in ShellRoute, goes back to "HomePage".

Code sample With this example, the "swipe to go back" works as expected:
GoRouter(
      initialLocation: '/home',
      routes: [
        GoRoute(
          path: '/home',
          name: HomePage.routeName,
          pageBuilder: const HomePage(),
          routes: [
             GoRoute(
                path: 'about',
                name: AboutThisAppPage.routeName,
                pageBuilder: const AboutThisAppPage(),
                routes: [
                  GoRoute(
                      path: 'acknowledgements',
                      name: AcknowledgementsPage.routeName,
                      pageBuilder: const AcknowledgementsPage(),
                  ),
                ]),
          ])
       ]);

When including a ShellRoute, swipe to go back from "AcknowledgementsPage" will go back to "HomePage" instead of "AboutThisAppPage".

GoRouter(
    initialLocation: '/home', 
    routes: [
     GoRoute(
        path: '/home',
        name: HomePage.routeName,
        pageBuilder: const HomePage(),
        routes: [
          // This minimal ShellRoute does not provide any benefit but is enough to reproduce the unexpected behaviour
          ShellRoute(
            builder: (context, state, child) {
              return child;
            },
            routes: [
              GoRoute(
                  path: 'about',
                  name: AboutThisAppPage.routeName,
                  pageBuilder: const AboutThisAppPage(),
                  routes: [
                    GoRoute(
                      path: 'acknowledgements',
                      name: AcknowledgementsPage.routeName,
                      pageBuilder: const AcknowledgementsPage(),
                    ),
                  ]),
            ]
          )
        ])
  ]);

Activity

  1. added
    in triagePresently being triaged by the triage team
    on Feb 9, 2023
  2. exaby73 commented on Feb 9, 2023

    @exaby73
    Member

    Hello @mnefzger. Thank you for filing this issue. Can you please provide the following information?

    Please provide the information in the form of text. See how to make collapsible sections with Markdown here.

  3. added
    waiting for responseThe Flutter team cannot make further progress on this issue until the original reporter responds
    on Feb 9, 2023
  4. mnefzger commented on Feb 9, 2023

    @mnefzger
    Author

    Hi @exaby73,
    sure – sorry for not adding a full example from the start.

    Expected: Swipe to go back on "ThirdPage" leads to "Second Page"
    Actual: Leads back to "HomePage"

    main.dart
    import 'package:flutter/material.dart';
    import 'package:go_router/go_router.dart';
    
    void main() {
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp.router(
            routerConfig: GoRouter(initialLocation: '/home', routes: [
          GoRoute(
              path: '/home',
              name: 'home',
              pageBuilder: const HomePage(),
              routes: [
                // This minimal ShellRoute does not provide any benefit but is enough to reproduce the unexpected behaviour
                ShellRoute(
                    builder: (context, state, child) {
                      return child;
                    },
                    routes: [
                      GoRoute(
                          path: 'second',
                          name: 'second',
                          pageBuilder: const SecondPage(),
                          routes: [
                            GoRoute(
                              path: 'third',
                              name: 'third',
                              pageBuilder: const ThirdPage(),
                            ),
                          ]),
                    ])
              ])
        ]));
      }
    }
    
    class HomePage {
      const HomePage();
    
      static void goTo(BuildContext context) {
        context.goNamed('home');
      }
    
      Page call(BuildContext context, GoRouterState routerState) {
        return MaterialPage(child: Builder(builder: (context) {
          return Scaffold(
            body: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  const Text("Home Page"),
                  OutlinedButton(
                      onPressed: () => SecondPage.goTo(context),
                      child: const Text("Go To Second")),
                ],
              ),
            ),
          );
        }));
      }
    }
    
    class SecondPage {
      const SecondPage();
    
      static void goTo(BuildContext context) {
        context.goNamed('second');
      }
    
      Page call(BuildContext context, GoRouterState routerState) {
        return MaterialPage(child: Builder(builder: (context) {
          return Scaffold(
            body: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  const Text("Second Page"),
                  OutlinedButton(
                      onPressed: () => ThirdPage.goTo(context),
                      child: const Text("Go To Third")),
                ],
              ),
            ),
          );
        }));
      }
    }
    
    class ThirdPage {
      const ThirdPage();
    
      static void goTo(BuildContext context) {
        context.goNamed('third');
      }
    
      Page call(BuildContext context, GoRouterState routerState) {
        return MaterialPage(child: Builder(builder: (context) {
          return const Scaffold(body: Center(child: Text("Third Page")));
        }));
      }
    }
    
    flutter doctor -v
    [✓] Flutter (Channel stable, 3.3.9, on macOS 13.0.1 22A400 darwin-arm, locale en-DE)
        • Flutter version 3.3.9 on channel stable at /Users/matthias.nefzger/flutter
        • Upstream repository https://github.com/flutter/flutter.git
        • Framework revision b8f7f1f986 (3 months ago), 2022-11-23 06:43:51 +0900
        • Engine revision 8f2221fbef
        • Dart version 2.18.5
        • DevTools version 2.15.0
    
    [!] Android toolchain - develop for Android devices (Android SDK version 33.0.0)
        • Android SDK at /Users/matthias.nefzger/Library/Android/sdk
        • Platform android-33, build-tools 33.0.0
        • Java binary at: /Applications/Android Studio.app/Contents/jre/Contents/Home/bin/java
        • Java version OpenJDK Runtime Environment (build 11.0.13+0-b1751.21-8125866)
        ✗ Android license status unknown.
          Run `flutter doctor --android-licenses` to accept the SDK licenses.
          See https://flutter.dev/docs/get-started/install/macos#android-setup for more details.
    
    [✓] Xcode - develop for iOS and macOS (Xcode 14.0.1)
        • Xcode at /Applications/Xcode.app/Contents/Developer
        • Build 14A400
        • CocoaPods version 1.11.3
    
    [✓] Chrome - develop for the web
        • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
    
    [✓] Android Studio (version 2021.3)
        • Android Studio at /Applications/Android Studio.app/Contents
        • Flutter plugin can be installed from:
          🔨 https://plugins.jetbrains.com/plugin/9212-flutter
        • Dart plugin can be installed from:
          🔨 https://plugins.jetbrains.com/plugin/6351-dart
        • Java version OpenJDK Runtime Environment (build 11.0.13+0-b1751.21-8125866)
    
    [✓] VS Code (version 1.71.2)
        • VS Code at /Applications/Visual Studio Code.app/Contents
        • Flutter extension version 3.56.0
    
    Scanning for devices is taking a long time...[✓] Connected device (1 available)
        • iPhone 13 Pro Max (mobile) • 26B46424-9068-4C85-BD99-D0F1B45959F7 • ios            • com.apple.CoreSimulator.SimRuntime.iOS-16-0 (simulator)
    
    [✓] HTTP Host Availability
        • All required HTTP hosts are available
    
    ! Doctor found issues in 1 category.
    
  5. removed
    waiting for responseThe Flutter team cannot make further progress on this issue until the original reporter responds
    on Feb 9, 2023
  6. exaby73 commented on Feb 10, 2023

    @exaby73
    Member

    Triage report

    I can reproduce this issue with Master (3.8.0-9.0.pre.20)

    Code Sample (Same as OP)
    import 'package:flutter/material.dart';
    import 'package:go_router/go_router.dart';
    
    void main() {
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp.router(
            routerConfig: GoRouter(initialLocation: '/home', routes: [
          GoRoute(
              path: '/home',
              name: 'home',
              pageBuilder: const HomePage(),
              routes: [
                // This minimal ShellRoute does not provide any benefit but is enough to reproduce the unexpected behaviour
                ShellRoute(
                    builder: (context, state, child) {
                      return child;
                    },
                    routes: [
                      GoRoute(
                          path: 'second',
                          name: 'second',
                          pageBuilder: const SecondPage(),
                          routes: [
                            GoRoute(
                              path: 'third',
                              name: 'third',
                              pageBuilder: const ThirdPage(),
                            ),
                          ]),
                    ])
              ])
        ]));
      }
    }
    
    class HomePage {
      const HomePage();
    
      static void goTo(BuildContext context) {
        context.goNamed('home');
      }
    
      Page call(BuildContext context, GoRouterState routerState) {
        return MaterialPage(child: Builder(builder: (context) {
          return Scaffold(
            body: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  const Text("Home Page"),
                  OutlinedButton(
                      onPressed: () => SecondPage.goTo(context),
                      child: const Text("Go To Second")),
                ],
              ),
            ),
          );
        }));
      }
    }
    
    class SecondPage {
      const SecondPage();
    
      static void goTo(BuildContext context) {
        context.goNamed('second');
      }
    
      Page call(BuildContext context, GoRouterState routerState) {
        return MaterialPage(child: Builder(builder: (context) {
          return Scaffold(
            body: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  const Text("Second Page"),
                  OutlinedButton(
                      onPressed: () => ThirdPage.goTo(context),
                      child: const Text("Go To Third")),
                ],
              ),
            ),
          );
        }));
      }
    }
    
    class ThirdPage {
      const ThirdPage();
    
      static void goTo(BuildContext context) {
        context.goNamed('third');
      }
    
      Page call(BuildContext context, GoRouterState routerState) {
        return MaterialPage(child: Builder(builder: (context) {
          return const Scaffold(body: Center(child: Text("Third Page")));
        }));
      }
    }
    flutter doctor -v (Master)
    [!] Flutter (Channel master, 3.8.0-9.0.pre.20, on macOS 13.2 22D49 darwin-arm64, locale en-US)
        • Flutter version 3.8.0-9.0.pre.20 on channel master at /Users/nabeelparkar/fvm/versions/master
        ! Warning: `dart` on your path resolves to /opt/homebrew/Cellar/dart/2.19.1/libexec/bin/dart, which is not inside your current Flutter SDK checkout at /Users/nabeelparkar/fvm/versions/master. Consider adding /Users/nabeelparkar/fvm/versions/master/bin to the front of your path.
        • Upstream repository https://github.com/flutter/flutter.git
        • Framework revision 001c4951b6 (7 hours ago), 2023-02-09 20:14:10 -0500
        • Engine revision e1d0032029
        • Dart version 3.0.0 (build 3.0.0-218.0.dev)
        • DevTools version 2.21.1
        • If those were intentional, you can disregard the above warnings; however it is recommended to use "git" directly to perform update checks and upgrades.
    
    [✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0)
        • Android SDK at /Users/nabeelparkar/Library/Android/sdk/
        • Platform android-33, build-tools 33.0.0
        • ANDROID_SDK_ROOT = /Users/nabeelparkar/Library/Android/sdk/
        • Java binary at: /Users/nabeelparkar/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/221.6008.13.2211.9477386/Android Studio.app/Contents/jbr/Contents/Home/bin/java
        • Java version OpenJDK Runtime Environment (build 11.0.15+0-b2043.56-8887301)
        • All Android licenses accepted.
    
    [✓] Xcode - develop for iOS and macOS (Xcode 14.2)
        • Xcode at /Applications/Xcode.app/Contents/Developer
        • Build 14C18
        • CocoaPods version 1.11.3
    
    [✓] Chrome - develop for the web
        • CHROME_EXECUTABLE = /Applications/Brave Browser.app/Contents/MacOS/Brave Browser
    
    [✓] Android Studio (version 2021.3)
        • Android Studio at /Users/nabeelparkar/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/213.7172.25.2113.9123335/Android Studio.app/Contents
        • Flutter plugin can be installed from:
          🔨 https://plugins.jetbrains.com/plugin/9212-flutter
        • Dart plugin can be installed from:
          🔨 https://plugins.jetbrains.com/plugin/6351-dart
        • Java version OpenJDK Runtime Environment (build 11.0.13+0-b1751.21-8125866)
    
    [✓] Android Studio (version 2022.1)
        • Android Studio at /Users/nabeelparkar/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/221.6008.13.2211.9477386/Android Studio.app/Contents
        • Flutter plugin can be installed from:
          🔨 https://plugins.jetbrains.com/plugin/9212-flutter
        • Dart plugin can be installed from:
          🔨 https://plugins.jetbrains.com/plugin/6351-dart
        • Java version OpenJDK Runtime Environment (build 11.0.15+0-b2043.56-8887301)
    
    [✓] VS Code (version 1.75.0)
        • VS Code at /Applications/Visual Studio Code.app/Contents
        • Flutter extension version 3.58.0
    
    [✓] Connected device (2 available)
        • macOS (desktop) • macos  • darwin-arm64   • macOS 13.2 22D49 darwin-arm64
        • Chrome (web)    • chrome • web-javascript • Brave Browser 110.1.48.158
    
    [✓] HTTP Host Availability
        • All required HTTP hosts are available
    
    ! Doctor found issues in 1 category.
    
  7. added
    platform-iosiOS applications specifically
    packageflutter/packages repository. See also p: labels.
    has reproducible stepsThe issue has been confirmed reproducible and is ready to work on
    on Feb 10, 2023
  8. added
    P2Important issues not at the top of the work list
    on Feb 14, 2023
  9. 31 remaining items

  10. added
    customer: crowdAffects or could affect many people, though not necessarily a specific customer.
    on Jun 26, 2025
  11. deandreamatias commented on Jul 3, 2025

    @deandreamatias

    Maybe this package help someone swipeable_page_route

  12. iamtechieboy commented on Jul 24, 2025

    @iamtechieboy

    Still experiencing this issue. Is there anyone fixed this issue ?

  13. iamtechieboy commented on Jul 24, 2025

    @iamtechieboy

    Why is there any solution for more than 2 years. I really disappointed. I like using go_router but what they didn't give attention on this issue yet.

  14. mustafamasri1 commented on Jul 24, 2025

    @mustafamasri1

    Just wrap you ShellRoute builder with :

    class ShellWrapper extends StatelessWidget {
      const ShellWrapper({
        super.key,
        required this.child,
      });
    
      final Widget child;
    
      @override
      Widget build(BuildContext context) {
        final canPop = _canPop(context);
        return PopScope(
          canPop: canPop,
          child: child,
        );
      }
    
      bool _canPop(BuildContext context) {
        // Skip if it's not IOS
        if (!Platform.isIOS) return true;
    
        final matches = GoRouter.of(context).routerDelegate.currentConfiguration.matches;
    
        final lastMatch = matches.lastOrNull;
        return lastMatch is ShellRouteMatch ? _isAtShellRoot(lastMatch) : true;
      }
    
      /// Loop until we're at the last non-shell route
      bool _isAtShellRoot(ShellRouteMatch match) {
        final lastNested = match.matches.lastOrNull;
    
        if (lastNested is ShellRouteMatch) {
          return _isAtShellRoot(lastNested);
        }
    
        // Consider we're at root only if the shell contains one route
        return match.matches.length <= 1;
      }
    }
    

    This support nested ShellRoutes also by looping and checking last ShellRoute

  15. iamtechieboy commented on Jul 24, 2025

    @iamtechieboy

    @mustafamasri1 Thanks bro. It works finally.

  16. github-actions commented on Oct 7, 2025

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

  17. locked as resolved and limited conversation to collaborators on Oct 7, 2025
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 listcustomer: crowdAffects or could affect many people, though not necessarily a specific customer.found in release: 3.7Found to occur in 3.7found in release: 3.8Found to occur in 3.8has reproducible stepsThe issue has been confirmed reproducible and is ready to work onp: go_routerThe go_router packagepackageflutter/packages repository. See also p: labels.platform-iosiOS applications specificallyteam-frameworkOwned by Framework teamtriaged-frameworkTriaged by Framework team

    Type

    No type

    Projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions