Flutter Navigator 2.0:解决无效URL导致404页面历史循环问题
问题现象
- 用户输入无效URL后,该URL被存入浏览器历史,同时路由跳转到404页面;
- 点击浏览器返回按钮时,路由回到无效URL,再次重定向到404页面,陷入循环;
- 仅能通过右键点击返回按钮选择上上个页面跳出循环。
复现代码
import 'package:flutter/material.dart'; class MyConfiguration { const MyConfiguration.details() : isDetail = true, isUnknown = false; const MyConfiguration.unknown() : isDetail = false, isUnknown = true; const MyConfiguration.home() : isDetail = false, isUnknown = false; final bool isDetail; final bool isUnknown; } class MyRouteParser extends RouteInformationParser<MyConfiguration> { const MyRouteParser() : super(); @override Future<MyConfiguration> parseRouteInformation( RouteInformation routeInformation) async { final location = routeInformation.location!; final uri = Uri.parse(location); if (uri.pathSegments.isEmpty) { return const MyConfiguration.home(); } if (uri.pathSegments.length == 1 && uri.pathSegments.first == 'details') { return const MyConfiguration.details(); } return const MyConfiguration.unknown(); } @override RouteInformation? restoreRouteInformation(MyConfiguration configuration) { if (configuration.isDetail) { return const RouteInformation(location: '/details'); } if (configuration.isUnknown) { return const RouteInformation(location: '/404'); // #1 } return const RouteInformation(location: '/'); } } class MyRouterDelegate extends RouterDelegate<MyConfiguration> with ChangeNotifier, PopNavigatorRouterDelegateMixin { @override MyConfiguration currentConfiguration = const MyConfiguration.unknown(); @override final navigatorKey = GlobalKey<NavigatorState>(); @override Future<void> setNewRoutePath(MyConfiguration configuration) async { currentConfiguration = configuration; } @override Widget build(BuildContext context) { final homePage = MaterialPage( key: const ValueKey('home'), child: Scaffold( appBar: AppBar(title: const Text('Home')), body: TextButton( onPressed: () { currentConfiguration = const MyConfiguration.details(); notifyListeners(); }, child: const Text('Show Details')), )); final detailPage = MaterialPage( key: const ValueKey('details'), child: Scaffold( appBar: AppBar(title: const Text('Details')), )); final unknownPage = MaterialPage( key: const ValueKey('unknown'), child: Scaffold( appBar: AppBar(title: const Text('Unknown')), )); return Navigator( key: navigatorKey, pages: [ homePage, if (currentConfiguration.isDetail) detailPage else if (currentConfiguration.isUnknown) unknownPage, ], onPopPage: (route, result) { if (!route.didPop(result)) { return false; } currentConfiguration = const MyConfiguration.home(); notifyListeners(); return true; }, ); } } void main() { runApp(MaterialApp.router( routeInformationParser: const MyRouteParser(), routerDelegate: MyRouterDelegate())); }
尝试方案及问题
修改restoreRouteInformation方法,当配置为unknown时返回null:
if (configuration.isUnknown) { return null; }
但此操作会导致地址栏无法显示/404路径。

解决方案
问题核心是:解析无效URL得到unknown配置后,restoreRouteInformation返回/404会让浏览器将/404添加到历史栈,但原无效URL仍保留在栈中。点击返回时,浏览器回到无效URL,再次被解析为unknown,重定向到/404形成循环。
解决思路是在解析无效URL时,直接替换历史栈中的无效记录,而非添加新记录:
1. 更新MyConfiguration,保存原始无效路径
class MyConfiguration { const MyConfiguration.details() : isDetail = true, isUnknown = false, invalidPath = null; const MyConfiguration.unknown([this.invalidPath]) : isDetail = false, isUnknown = true; const MyConfiguration.home() : isDetail = false, isUnknown = false, invalidPath = null; final bool isDetail; final bool isUnknown; final String? invalidPath; // 新增:存储原始无效路径 }
2. 修改RouteParser,传递无效路径
@override Future<MyConfiguration> parseRouteInformation( RouteInformation routeInformation) async { final location = routeInformation.location!; final uri = Uri.parse(location); if (uri.pathSegments.isEmpty) { return const MyConfiguration.home(); } if (uri.pathSegments.length == 1 && uri.pathSegments.first == 'details') { return const MyConfiguration.details(); } // 若为/404,直接返回无invalidPath的unknown if (uri.path == '/404') { return const MyConfiguration.unknown(); } // 其他无效路径,传递原始路径 return MyConfiguration.unknown(location); }
3. 重写RouterDelegate的setNewRoutePath方法处理重定向
需要先导入flutter/services.dart包:
import 'package:flutter/services.dart'; @override Future<void> setNewRoutePath(MyConfiguration configuration) async { if (configuration.isUnknown && configuration.invalidPath != null) { // 替换历史栈中的无效URL为/404 SystemNavigator.routeInformationUpdated( location: '/404', state: null, ); // 更新当前配置为无invalidPath的unknown currentConfiguration = const MyConfiguration.unknown(); } else { currentConfiguration = configuration; } notifyListeners(); }
4. 保留restoreRouteInformation原有逻辑
确保地址栏显示/404:
@override RouteInformation? restoreRouteInformation(MyConfiguration configuration) { if (configuration.isDetail) { return const RouteInformation(location: '/details'); } if (configuration.isUnknown) { return const RouteInformation(location: '/404'); } return const RouteInformation(location: '/'); }
效果说明
- 用户输入无效URL时,浏览器历史栈中的无效记录会被替换为
/404,不会保留无效URL; - 点击返回按钮时,直接回到上一个有效页面(如Home或Details),不会陷入循环;
- 地址栏始终显示
/404,符合预期。
内容的提问来源于stack exchange,提问作者andreas1724
相关产品推荐
相关产品推荐

