浏览器的后退按钮不触发 parseRouteInformation 并抛出 Duplicate GlobalKey detected in widget tree.error Flutter
Posted
技术标签:
【中文标题】浏览器的后退按钮不触发 parseRouteInformation 并抛出 Duplicate GlobalKey detected in widget tree.error Flutter【英文标题】:Browser's back button doesn't trigger parseRouteInformation and throws Duplicate GlobalKey detected in widget tree.error Flutter 【发布时间】:2022-01-05 04:47:39 【问题描述】:我正在切换到Navigator 2.0
,我可以设法使用NavigationBar
小部件中的导航按钮更改页面,但是当点击浏览器的后退或前进按钮时,页面不会更新显示的网址。
如果我再次按下它,它确实会返回,但会引发 Duplicate GlobalKey detected in widget tree.
错误。
The following assertion was thrown while finalizing the widget tree:
Duplicate GlobalKey detected in widget tree.
The following GlobalKey was specified multiple times in the widget tree. This will lead to parts of the widget tree being truncated unexpectedly, because the second time a key is seen, the previous instance is moved to the new location. The key was:
- [LabeledGlobalKey<NavigatorState>#781da]
This was determined by noticing that after the widget with the above global key was moved out of its previous parent, that previous parent never updated during this frame, meaning that it either did not update at all or updated before the widget was moved, in either case implying that it still thinks that it should have a child with that global key.
The specific parent that did not update after having one or more children forcibly removed due to GlobalKey reparenting is:
- Builder
A GlobalKey can only be specified on one widget at a time in the widget tree.
When the exception was thrown, this was the stack:
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 251:49 throw_
packages/flutter/src/widgets/framework.dart 2939:15 <fn>
packages/flutter/src/widgets/framework.dart 2963:16 finalizeTree
packages/flutter/src/widgets/binding.dart 884:7 drawFrame
packages/flutter/src/rendering/binding.dart 319:5 [_handlePersistentFrameCallback]
packages/flutter/src/scheduler/binding.dart 1143:15 [_invokeFrameCallback]
packages/flutter/src/scheduler/binding.dart 1080:9 handleDrawFrame
packages/flutter/src/scheduler/binding.dart 996:5 [_handleDrawFrame]
lib/_engine/engine/platform_dispatcher.dart 1003:13 invoke
lib/_engine/engine/platform_dispatcher.dart 157:5 invokeOnDrawFrame
lib/_engine/engine.dart 440:45 <fn>
我所有的小部件,无论它们是无状态操作系统,都有const WidgetName(Key key) : super(key: key)
构造函数,并且在实例化它们时,我从不将父级的密钥传递给子级..
我尝试从小部件的构造函数中删除 Key
,但仍然收到错误消息。..
我在 RouterDelegate
中从 Navigator 中删除了密钥,重复错误消失了,但仍然需要按 2 次后退按钮才能返回上一页,并且手动编辑 url 会引发错误:
Could not navigate to initial route.
The requested route name was: "/retailers"
There was no corresponding route in the app, and therefore the initial route specified will be ignored and "/" will be used instead.
在我覆盖的parseRouteInformation
中,我设置了打印,它显示应用程序何时启动,但当我按下浏览器的后退或前进按钮时不显示。
你能看出为什么RouteInformationParser
没有解析之前的路由并抛出Duplicate GlobalKey detected in widget tree.
错误吗?
我检查了一下,RouterDelegate
中的那个是我声明的唯一一个。
关于我的应用,您还有什么需要查看的吗?
这是我的 RouterDelegate:
class AppRouterDelegate extends RouterDelegate<RoutePath>
with ChangeNotifier, PopNavigatorRouterDelegateMixin<RoutePath>
final GlobalKey<NavigatorState> navigatorKey;
AppState appState = AppState();
AppRouterDelegate() : navigatorKey = GlobalKey<NavigatorState>()
appState.addListener(notifyListeners);
print('appState.addListener(notifyListeners) called');
@override
RoutePath get currentConfiguration
print(
'RouterDelegate.currentConfiguration appState.selectedPage is $appState.selectedPage');
switch (appState.selectedPage)
case '/':
return HomePath();
case CyclistsLandingRoute:
return CyclistsPath();
case RetailersLandingRoute:
return RetailersPath();
case MapRoute:
return MapPath();
case AboutRoute:
return AboutPath();
case TermsOfServiceRoute:
return TermsOfServicePath();
case PrivacyPolicyRoute:
return PrivacyPolicyPath();
case PrivacySettingsRoute:
return PrivacySettingsPath();
case CommunityGuidelinesRoute:
return CommunityGuidelinesPath();
case LegalNoticeRoute:
return LegalPath();
default:
return HomePath();
@override
Widget build(BuildContext context)
print("Delegate build");
return Navigator(
key: navigatorKey,
pages: [
MaterialPage(
child: WebsitePageDisplay(
appState: appState,
))
],
onPopPage: (route, result)
print("onPopPage");
if (!route.didPop(result))
return false;
if (appState.selectedPage != null)
appState.selectedPage = null;
notifyListeners();
return true;
);
@override
Future<void> setNewRoutePath(RoutePath path) async
print('RouterDelegate.setNewRoutePath path is $path.selectedPath');
appState.selectedPage = path.selectedPath;
这是我的 RouteInformationParser:
class AppRouteInformationParser extends RouteInformationParser<RoutePath>
@override
Future<RoutePath> parseRouteInformation(
RouteInformation routeInformation) async
print(
'AppRouteInformationParser.parseRouteInformation called for $routeInformation.location');
final Uri uri = Uri.parse(routeInformation.location);
if (uri.pathSegments.length > 0)
print(
'Uri.segments.first is: $uri.pathSegments.first, uri.path is: $uri.path');
else
print('AppRouteInformationParser uri has no segments and is $uri');
switch (routeInformation.location)
// switch (uri.pathSegments.first)
case '/':
print('AppRouteInformationParser.urlSegment switch case : /');
// return CyclistsPath();
return HomePath();
case CyclistsLandingRoute:
print(
'AppRouteInformationParser.routeInformation.location switch case: /cyclists');
return CyclistsPath();
case '/retailers':
print(
'AppRouteInformationParser.routeInformation.location switch case: /retailers');
return RetailersPath();
case '/map':
print(
'AppRouteInformationParser.routeInformation.location switch case: /map');
return MapPath();
case AboutRoute:
print(
'AppRouteInformationParser.routeInformation.location switch case: /about');
return AboutPath();
case TermsOfServiceRoute:
print(
'AppRouteInformationParser.routeInformation.location switch case: /terms-of-service');
return TermsOfServicePath();
case PrivacyPolicyRoute:
print(
'AppRouteInformationParser.routeInformation.location switch case: /privacy-policy');
return PrivacyPolicyPath();
case PrivacySettingsRoute:
print(
'AppRouteInformationParser.routeInformation.location switch case: /privacy-settings');
return PrivacySettingsPath();
case CommunityGuidelinesRoute:
print(
'AppRouteInformationParser.routeInformation.location switch case: /community-guidelines');
return CommunityGuidelinesPath();
case LegalNoticeRoute:
print(
'AppRouteInformationParser.routeInformation.location switch case: /legal-notice');
return LegalPath();
default:
print(
'### default AppRouteInformationParser.routeInformation.location switch case ## default: /');
return HomePath();
@override
RouteInformation restoreRouteInformation(RoutePath path)
print(
'AppRouteInformationParser.restoreRouteInformation called for path $path.selectedPath');
switch (path.selectedPath)
case '/':
// case CyclistsLandingRoute:
print('restoreRouteInformation RouteInformation.location: /');
return RouteInformation(location: '/');
case '/cyclists':
// case CyclistsLandingRoute:
print('restoreRouteInformation RouteInformation.location: /cyclists');
return RouteInformation(location: '/cyclists');
case '/retailers':
print('restoreRouteInformation RouteInformation.location: /retailers');
return RouteInformation(location: '/retailers');
case '/map':
print('restoreRouteInformation RouteInformation.location: /map');
return RouteInformation(location: '/map');
case '/about':
print('restoreRouteInformation RouteInformation.location: /about');
return RouteInformation(location: '/about');
case '/terms-of-service':
print(
'restoreRouteInformation RouteInformation.location: /terms-of-service');
return RouteInformation(location: '/terms-of-service');
case '/privacy-policy':
print(
'restoreRouteInformation RouteInformation.location: /privacy-policy');
return RouteInformation(location: '/privacy-policy');
case '/privacy-settings':
print(
'restoreRouteInformation RouteInformation.location: /privacy-settings');
return RouteInformation(location: '/privacy-settings');
case '/community-guidelines':
print(
'restoreRouteInformation RouteInformation.location: /community-guidelines');
return RouteInformation(location: '/community-guidelines');
case '/legal-notice':
print(
'restoreRouteInformation RouteInformation.location: /legal-notice');
return RouteInformation(location: '/legal-notice');
default:
print(
'restoreRouteInformation ### Default RouteInformation.location: /cyclists');
return RouteInformation(location: '/cyclists');
这是 AppState:
class AppState extends ChangeNotifier
String _selectedPage;
AppState() : _selectedPage = null;
String get selectedPage => _selectedPage;
Widget get selectedWidget
switch (selectedPage)
case CyclistsLandingRoute:
return CyclistLanding();
break;
case RetailersLandingRoute:
return RetailerLanding();
break;
case MapRoute:
return CityMap();
break;
case AboutRoute:
return AboutUs();
break;
case TermsOfServiceRoute:
return TermsOfService();
break;
case PrivacyPolicyRoute:
return PrivacyPolicy();
break;
// case PrivacySettingsRoute:
// return PrivacyPolicySettings();
// break;
case CommunityGuidelinesRoute:
return CommunityGuidelines();
break;
case LegalNoticeRoute:
return LegalNotice();
break;
default:
return CyclistLanding();
notifyListeners();
notifyListeners();
set selectedPage(String page)
print('AppState setting selectedPage to $page');
_selectedPage = page;
notifyListeners();
【问题讨论】:
【参考方案1】:我终于找到了问题:
在 main.dart 构建方法中,我返回一个 MaterialApp
和一个 MaterialApp.router
作为主地址,而不是直接返回一个 MaterialApp.router
并将所有参数移入其中。
现在一切正常。
错误的方式:
AppRouterDelegate _routerDelegate = AppRouterDelegate();
AppRouteInformationParser _routeInformationParser =
AppRouteInformationParser();
@override
Widget build(BuildContext context)
return MaterialApp(
title: '',
color: Colors.red,
localizationsDelegates: [
const AppLocalizationsDelegate(),
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: [
const Locale('en', 'US'),
const Locale('it', 'IT')
// const Locale('es', 'ES'),
],
localeResolutionCallback:
(Locale locale, Iterable<Locale> supportedLocales)
for (Locale supportedLocale in supportedLocales)
if (supportedLocale.languageCode == locale.languageCode ||
supportedLocale.countryCode == locale.countryCode)
print('Web device Locale is $locale');
return supportedLocale;
return supportedLocales.first;
,
debugShowCheckedModeBanner: false,
home: MaterialApp.router(
routeInformationParser: _routeInformationParser,
routerDelegate: _routerDelegate),
);
正确的方法是:
AppRouterDelegate _routerDelegate = AppRouterDelegate();
AppRouteInformationParser _routeInformationParser =
AppRouteInformationParser();
@override
Widget build(BuildContext context)
return MaterialApp.router(
routeInformationParser: _routeInformationParser,
routerDelegate: _routerDelegate,
debugShowCheckedModeBanner: false,
title: '',
color: Colors.red,
localizationsDelegates: [
const AppLocalizationsDelegate(),
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: [
const Locale('en', 'US'),
const Locale('it', 'IT')
// const Locale('es', 'ES'),
],
localeResolutionCallback:
(Locale locale, Iterable<Locale> supportedLocales)
for (Locale supportedLocale in supportedLocales)
// if (UniversalPlatform.isWeb)
if (supportedLocale.languageCode == locale.languageCode ||
supportedLocale.countryCode == locale.countryCode)
print('Web device Locale is $locale');
return supportedLocale;
return supportedLocales.first;
,
// localeListResolutionCallback: ,
);
【讨论】:
以上是关于浏览器的后退按钮不触发 parseRouteInformation 并抛出 Duplicate GlobalKey detected in widget tree.error Flutter的主要内容,如果未能解决你的问题,请参考以下文章
后退按钮未触发 Chrome 中“beforeunload”中的方法
Angular 10:使用浏览器的后退按钮从外部 URL 导航回来时,ngOnInit 未在 Firefox 中部署的应用程序版本中触发