在Flutter中Go路由器的popUntil的替代方法

6

我在想是否有Navigator的popUntil()方法的替代方案,或者任何实现相同效果的变通方法。

如果我要使用Navigator,我会像这样使用:

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

如何在go路由器中实现相同的功能?


我看到了一个 Github 的 issue - [go_router] Implement popUntil #2728,但是没有任何积极的结果。


您的意思是要前往“登录”路由,删除其下的所有路由并保留其上方的路由(如果有的话)吗? - Peter Koltai
@PeterKoltai,我的意思是,如果您将导航视为堆栈,则我希望弹出所有内容,直到“login”,并且下面的任何内容保持不变。 - Agni Gari
仅仅使用 context.goNamed('/login') 在这种情况下是不起作用的吗?如果有一些路由没有包含在 /login 命名路由中,它们将会从栈中移除。 - Peter Koltai
5个回答

9

解决方法


虽然@CommonSense的答案可行,但我们可以稍微调整一下,避免在GoRouter无法弹出时使用pop。

请记住这一点。最后,这只是一个解决方法,未来特别是在GoRouter升级方面可能会导致意外行为。

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

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

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

正确的方式?


另一种实现该功能的方式(似乎是正确的实现方式),是使用带有正确路由声明的 .go,像这样:

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'),
            ),
          ],
        ),
      ),
    );
  }
}



当您调用goNamed/login时,它会执行弹出操作直到登录,这正是您想要的。
如果您没有像这样声明路由(也许您正在使用命令式API),GoRouter将会go到该路由,但是通过将其添加到堆栈中而不仅仅是删除其他路由。

我可以确认:我尝试了“正确的方法”,它完全按预期工作!谢谢! - matteoh
我可以确认:我尝试了“正确的方法”,它完全按预期运行!谢谢! - matteoh
我可以确认:我尝试了“正确的方法”,它完全按照预期工作!谢谢! - undefined
话虽如此,你的解决方法在复杂导航中可能更加方便。 - matteoh
话虽如此,你的解决方法在复杂导航中可能更加方便。 - matteoh
话虽如此,你的变通方法在复杂导航中可能更加方便。 - undefined

5

go_router: 10.2.0

///Navigate back to specific route
void popUntilPath(BuildContext context, String routePath) {
 while (
  router.routerDelegate.currentConfiguration.matches.last.matchedLocation !=
      routePath) {
  if (!context.canPop()) {
  return;
  }
 context.pop();
 }
}

4

希望这能有所帮助

while (GoRouter.of(context).location != "/") {
    debugPrint(GoRouter.of(context).location.toString()); //to get the routes in between
    GoRouter.of(context).pop();
}

它能够工作,但有时候似乎可以看到页面被弹出。 - Acácio Veit Schneider
Acacio 你可以使用过渡材料或其他方法。 - Common Sense
是的,但你也需要改变推送的过渡,对吧? - Acácio Veit Schneider
不一定。你所寻找的问题可能是GoRouter旧版本中的错误,但在这个拉取请求中已经修复了。如果你仍然感觉存在某些弹出转换问题,你可以在其中打开一个修复请求问题,我们会进一步研究它。 - Common Sense
不一定。你所寻找的可能是GoRouter旧版本中的一个错误,但在这个拉取请求中已经修复了。如果你仍然觉得存在某种弹出过渡问题,你可以在其中打开一个修复请求问题,我们会进行调查。 - Common Sense
显示剩余2条评论

1

使用 go_router: ^9.0.1 测试了这个解决方案

创建一个类并扩展 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();
  }
}

在启动GoRouter时,通过观察者。

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

使用此功能直到您所需的路线弹出
void popUntil(String routeName) {
  if (!MyNavigatorObserver.backStack.contains(routeName))
      return;

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

1
我正在使用以下的扩展来实现这个功能。
extension GoRouterExtension on GoRouter {
  // Navigate back to a specific route
  void popUntilPath(BuildContext context, String ancestorPath) {
    while (routerDelegate.currentConfiguration.matches.last.matchedLocation !=
        ancestorPath) {
      if (!context.canPop()) {
        return;
      }
      context.pop();
    }
  }
}

这个叫号器会是这样的:
GoRouter.of(context).popUntilPath(context, desiredAncestorPath);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接