4

I was wonder if there is alternative to Navigator's popUntil() method, or any workaround to achieve the same.

If i were to use Navigator. I would use like so:

void _logout() {
  Navigator.popUntil(context, ModalRoute.withName('/login'));
}

How to achieve the same in go router ?


I see a github issue - [go_router] Implement popUntil #2728, but there is no positive outcome of the same.

krishnaacharyaa
  • 14,953
  • 4
  • 49
  • 88
  • Do you mean you want to go to the `login` route, remove all routes below it and keep routes above it (if any)? – Peter Koltai Mar 29 '23 at 07:43
  • @PeterKoltai, I mean if you see navigation as a stack I want to pop everything till `login` and anything below it remains as is – krishnaacharyaa Mar 29 '23 at 08:05
  • Simply using `context.goNamed('/login')` does not work in this case? If there are routes which are not included in `/login` named route, they will be removed from the stack. – Peter Koltai Mar 29 '23 at 15:10

3 Answers3

4

WORKAROUND


While the answer from @CommonSense works, maybe we could tweak it a little, avoiding pop when GoRouter cannot pop.

And have this in mind. At the end, this is just a workaround and can leave to unexpected behaviors, specially with GoRouter upgrades in the future.

void popUntilPath(String routePath) {
    
  final router = GoRouter.of(context);
  while (router.location != routePath) {

    if (!router.canPop()) {
      return;
    }

    debugPrint('Popping ${router.location}');
    router.pop();
  }
}

THE RIGHT WAY?


Another way to do that (which seems to be the right way of doing this) is to use .go with the correct route declaration, like this:

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: 'Go Router Demo',
      routerConfig: GoRouter(
        initialLocation: '/login',
        routes: [
          GoRoute(
            name: '/login',
            path: '/login',
            builder: (_, __) => const Login(),
            routes: [
              GoRoute(
                name: 'step-1',
                path: 'step-1',
                builder: (_, __) => const Step1(),
              ),
              GoRoute(
                name: 'step-2',
                path: 'step-2',
                builder: (_, __) => const Step2(),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

class Login extends StatelessWidget {
  const Login({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Entry Page')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('Login entry page'),
            ElevatedButton(
              onPressed: () => GoRouter.of(context).pushNamed('step-1'),
              child: const Text('Go to step 1'),
            ),
          ],
        ),
      ),
    );
  }
}

class Step1 extends StatelessWidget {
  const Step1({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Step 1')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('Step 1'),
            ElevatedButton(
              onPressed: () => GoRouter.of(context).pushNamed('step-2'),
              child: const Text('Go to step 2'),
            ),
          ],
        ),
      ),
    );
  }
}

class Step2 extends StatelessWidget {
  const Step2({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Step 2')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('Step 2'),
            ElevatedButton(
              onPressed: () => GoRouter.of(context).goNamed('/login'),
              child: const Text('Go to login'),
            ),
          ],
        ),
      ),
    );
  }
}


When you call goNamed to /login, it will do a pop until login, which is what you want.

If you don't declare you routes like this (maybe you are using the imperative API) the GoRouter will go to that route but by adding it into the stack and not just removing the other routes.

3

Hope this helps

while (GoRouter.of(context).location != "/") {
    debugPrint(GoRouter.of(context).location.toString()); //to get the routes in between
    GoRouter.of(context).pop();
}
Common Sense
  • 1
  • 2
  • 14
0

Tested this solution with go_router: ^9.0.1

Create a class and extend NavigatorObserver.

class MyNavigatorObserver extends NavigatorObserver {
  static List<String> backStack = [];

  @override
  void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
    backStack.add(route.settings.name ?? '');
  }

  @override
  void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
    backStack.removeLast();
  }
}

Pass the observer while initiating GoRouter

GoRouter(
  initialLocation: initialLocation,
  observers: [ MyNavigatorObserver() ],
  ...
  ...
)

Use this function to pop until your desired route

void popUntil(String routeName) {
  if (!MyNavigatorObserver.backStack.contains(routeName))
      return;

  while (MyNavigatorObserver.backStack.last != RouteNames.bottomNav) {
    GoRouter.of(context).pop();
  }
}