getx icon indicating copy to clipboard operation
getx copied to clipboard

Router issue after clicking too fast on navigation buttons

Open AlexanderThiele opened this issue 3 years ago • 7 comments

Hello everyone! We have a bottomNavigationbar in our RootPage() which changes the route on click for example to /home or to /psf.

Everything works fine if you click slowly but whenever I click faster, the navigation stops completely with the following error:

======== Exception caught by widgets library =======================================================
The following assertion was thrown building GetRouterOutlet(state: _RouterOutletState<GetDelegate, GetNavConfig>#357e4):
'package:flutter/src/widgets/navigator.dart': Failed assertion: line 3630 pos 14: '!pageKeyToOldEntry.containsKey(page.key)': is not true.

Either the assertion indicates an error in the framework itself, or we should provide substantially more information in this error message to help you determine and fix the underlying cause.
In either case, please report this assertion by filing a bug on GitHub:
  https://github.com/flutter/flutter/issues/new?template=2_bug.md

The relevant error-causing widget was: 
  GetRouterOutlet GetRouterOutlet:file:///Users/.../lib/pages/root/root_page.dart:42:15
When the exception was thrown, this was the stack: 
#2      NavigatorState._updatePages (package:flutter/src/widgets/navigator.dart:3630:14)
#3      NavigatorState.didUpdateWidget (package:flutter/src/widgets/navigator.dart:3433:7)

...

followed by:

======== Exception caught by rendering library =====================================================
The following assertion was thrown during performLayout():
Each child must be laid out exactly once.

The _ScaffoldLayout custom multichild layout delegate forgot to lay out the following child: 
  _ScaffoldSlot.body: RenderErrorBox#6d3fa NEEDS-LAYOUT NEEDS-PAINT
    parentData: offset=Offset(0.0, 0.0); id=_ScaffoldSlot.body
    constraints: MISSING
    size: MISSING
The relevant error-causing widget was: 
  Scaffold Scaffold:file:///Users/alexanderthiele/projects/hermes/mobile-app/lib/pages/root/root_page.dart:41:14
When the exception was thrown, this was the stack: 
#0      MultiChildLayoutDelegate._callPerformLayout.<anonymous closure> (package:flutter/src/rendering/custom_layout.dart:243:11)
#1      MultiChildLayoutDelegate._callPerformLayout (package:flutter/src/rendering/custom_layout.dart:255:8)
#2      RenderCustomMultiChildLayoutBox.performLayout (package:flutter/src/rendering/custom_layout.dart:403:14)
#3      RenderObject._layoutWithoutResize (package:flutter/src/rendering/object.dart:1707:7)
...

The navigation stops completely and after a hot reload the following error appears:

======== Exception caught by widgets library =======================================================
The following assertion was thrown building GetRouterOutlet(state: _RouterOutletState<GetDelegate, GetNavConfig>#357e4):
A GlobalKey was used multiple times inside one widget's child list.

The offending GlobalKey was: [LabeledGlobalKey<NavigatorState>#9688d Getx nested key: /home]
The parent of the widgets with that key was: GetRouterOutlet
  state: _RouterOutletState<GetDelegate, GetNavConfig>#357e4
The first child to get instantiated with that key became: GetNavigator-[LabeledGlobalKey<NavigatorState>#9688d Getx nested key: /home]
  dirty
  dependencies: [_EffectiveTickerMode, HeroControllerScope, UnmanagedRestorationScope]
  state: NavigatorState#7e95a(tickers: tracking 3 tickers)
The second child that was to be instantiated with that key was: GetRouterOutlet
A GlobalKey can only be specified on one widget at a time in the widget tree.
The relevant error-causing widget was: 
  GetRouterOutlet GetRouterOutlet:file:///Users/.../lib/pages/root/root_page.dart:42:15
When the exception was thrown, this was the stack: 
#0      Element._retakeInactiveElement.<anonymous closure> (package:flutter/src/widgets/framework.dart:3568:11)
#1      Element._retakeInactiveElement (package:flutter/src/widgets/framework.dart:3582:8)
#2      Element.inflateWidget (package:flutter/src/widgets/framework.dart:3613:33)

followed by:

======== Exception caught by widgets library =======================================================
The following assertion was thrown building GetRouterOutlet(state: _RouterOutletState<GetDelegate, GetNavConfig>#357e4):
A GlobalKey was used multiple times inside one widget's child list.

The offending GlobalKey was: [LabeledGlobalKey<NavigatorState>#9688d Getx nested key: /home]
The parent of the widgets with that key was: GetRouterOutlet
  state: _RouterOutletState<GetDelegate, GetNavConfig>#357e4
The first child to get instantiated with that key became: GetNavigator-[LabeledGlobalKey<NavigatorState>#9688d Getx nested key: /home]
  dirty
  dependencies: [_EffectiveTickerMode, HeroControllerScope, UnmanagedRestorationScope]
  state: NavigatorState#7e95a(tickers: tracking 3 tickers)
The second child that was to be instantiated with that key was: GetRouterOutlet
A GlobalKey can only be specified on one widget at a time in the widget tree.
The relevant error-causing widget was: 
  GetRouterOutlet GetRouterOutlet:file:///Users/.../lib/pages/root/root_page.dart:42:15
When the exception was thrown, this was the stack: 
#0      Element._retakeInactiveElement.<anonymous closure> (package:flutter/src/widgets/framework.dart:3568:11)
#1      Element._retakeInactiveElement (package:flutter/src/widgets/framework.dart:3582:8)
#2      Element.inflateWidget (package:flutter/src/widgets/framework.dart:3613:33)

Our Implementation

The bottom navigation always stays inside the RootPage() and has a GetRouterOutlet.

@override
  Widget build(BuildContext context) {
    return GetRouterOutlet.builder(builder: (context, delegate, currentRoute) {
      //This router outlet handles the the bottom navigation bar
      final currentLocation = currentRoute?.location;
      var currentIndex = _determineCurrentIndex(currentLocation);

      return Scaffold(
        body: GetRouterOutlet(
          initialRoute: Routes.HOME,
          key: Get.nestedKey(Routes.HOME),
        ),
        bottomNavigationBar: CustomBottomNavigation(
          width: MediaQuery.of(context).size.width,
          currentIndex: currentIndex,
          onTap: (value) => _onTap(value, delegate),
        ),
      );
    });
  }

void _onTap(int value, GetDelegate delegate) {
    String? route = RoutesIndex.values
        .firstWhere((routesIndex) => routesIndex.index == value)
        .route;

    if (route != null) {
      delegate.toNamed(route);
    }
  }

The Routes are defined as follows:

static final routes = [
    GetPage(
      name: _Paths.ROOT,
      page: () => const RootPage(),
      binding: RootBinding(),
      participatesInRootNavigator: true,
      preventDuplicates: true,
      children: [
        GetPage(
          preventDuplicates: true,
          name: _Paths.HOME,
          page: () => const HomePage(),
          binding: HomeBinding(),
        ),
        GetPage(
          preventDuplicates: true,
          name: _Paths.PSF,
          page: () => const PsfPage(),
        ),
      ],
    )
  ];

and the HomeBinding:

class HomeBinding extends Bindings {
  @override
  void dependencies() {
    Get.lazyPut(() => HomeController());
  }
}

Main App is:

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp.router(
      title: "MyApp",
      initialBinding: RootBinding(),
      getPages: AppPages.routes,
      defaultTransition: Transition.noTransition,
      theme: MyThemes.myTheme,
      debugShowCheckedModeBanner: false,
    );
  }
}

The Controller log before the issue appears is:


[GETX] "HomeController" onDelete() called
[GETX] "HomeController" deleted from memory
[GETX] Instance "HomeController" has been created
[GETX] Instance "HomeController" has been initialized
[GETX] "HomeController" onDelete() called
[GETX] "HomeController" deleted from memory
[GETX] Instance "HomeController" has been created
[GETX] Instance "HomeController" has been initialized
[GETX] "HomeController" onDelete() called
[GETX] "HomeController" deleted from memory
[GETX] Instance "HomeController" has been created
[GETX] Instance "HomeController" has been initialized

I have a feeling that the navigation is trying to use the controller that is about to get destroyed and then produces the crash.

I'm not sure if it's an implementation issue or a general routing issue.

Flutter Version: 2.8.0

Getx Version: 4.6.1

Describe on which device you found the bug: Android Pixel 3, ios Simulator iPhone 13

AlexanderThiele avatar Jan 31 '22 14:01 AlexanderThiele

A GlobalKey was used multiple times inside one widget's child list.

It means more than 1 widget uses the same GlobalKey which is Get.nestedKey(Routes.HOME) in your case. I will check this first if I were you.

XuanTung95 avatar Feb 01 '22 14:02 XuanTung95

Hey @XuanTung95 Yes, but i'm not managing the lifecycle of the controllers when routing, GetX is managing them afaik and i think that the controller is still in the process of destroying.

AlexanderThiele avatar Feb 01 '22 16:02 AlexanderThiele

What does the controller have to do with globalkey error? I think your case is not about the controller, delegate.toNamed(route) may open 2 pages that use the same globalKey.

Anyway, we know controller error in some rare cases as #1880.

XuanTung95 avatar Feb 02 '22 05:02 XuanTung95

Hey @XuanTung95 ok (i had a similar issue somewhere else where it brought up the controllers) but the GlobalKey is only used on a single page. That's why I said moving fast back and forth to the same page.

AlexanderThiele avatar Feb 02 '22 09:02 AlexanderThiele

getX official demo example_nav2 have a similar issue, not solved yet

rayscodelife avatar Feb 05 '22 15:02 rayscodelife


part of 'app_pages.dart';
// DO NOT EDIT. This is code generated via package:get_cli/get_cli.dart

abstract class Routes {
  static const HOME = _Paths.HOME;

  static const PROFILE = _Paths.HOME + _Paths.PROFILE; // try with nested _Paths
  static const SETTINGS = _Paths.SETTINGS;

  static const PRODUCTS = _Paths.HOME + _Paths.PRODUCTS; // try with nested _Paths

  static const LOGIN = _Paths.LOGIN;
  static const DASHBOARD = _Paths.HOME + _Paths.DASHBOARD; // try with nested _Paths
  Routes._();
  static String LOGIN_THEN(String afterSuccessfulLogin) =>
      '$LOGIN?then=${Uri.encodeQueryComponent(afterSuccessfulLogin)}';
  static String PRODUCT_DETAILS(String productId) => '$PRODUCTS/$productId';
}

abstract class _Paths {
  static const HOME = '/home';
  static const PRODUCTS = '/products';
  static const PROFILE = '/profile';
  static const SETTINGS = '/settings';
  static const PRODUCT_DETAILS = '/:productId';
  static const LOGIN = '/login';
  static const DASHBOARD = '/dashboard';
}`

rhozul avatar Sep 28 '23 15:09 rhozul

is there any solutions?

dengliwenDecode avatar Dec 21 '23 14:12 dengliwenDecode