浏览器的后退按钮不会触发 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

我正在切换到 Navigator 2.0,我可以设法使用 NavigationBar 小部件中的导航按钮更改页面,但是当点击浏览器的后退或前进按钮时,页面不会更新显示的面团url 确实如此。 如果我第二次按面团,它会返回但会抛出 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>

我所有的小部件,无论它们是无状态的 os 有状态的,都具有 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();
  }
}

我终于找到问题了:

在 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: ,
    );
  }