Shell route reverts back to default

I my app I use riverpod to provide the routing (ShellRoute) to my page, it reverts back to the default route, anyone have a sharp eye as to where my mistake lie?
My routes

class AppRoutes {
  static final router = GoRouter(
      routerNeglect: true,
      initialLocation: SplashPage.route,
      navigatorKey: Utils.mainNav,
      routes: [
        GoRoute(
          parentNavigatorKey: Utils.mainNav,
          path: SplashPage.route,
          builder: (context, state) {
            return const SplashPage();
          },
        ),
        GoRoute(
          parentNavigatorKey: Utils.mainNav,
          path: LoadingPage.route,
          builder: (context, state) {
            return const LoadingPage();
          },
        ),
        ShellRoute(
            navigatorKey: Utils.tabNav,
            builder: (context, state, child) {
              return MainPage(child: child);
            },
            routes: [
              GoRoute(
                parentNavigatorKey: Utils.tabNav,
                path: HomePage.route,
                pageBuilder: (context, state) {
                  return const NoTransitionPage(
                    child: HomePage(),
                  );
                },
              ),
              GoRoute(
                parentNavigatorKey: Utils.tabNav,
                path: RoomsPage.route,
                pageBuilder: (context, state) {
                  return const NoTransitionPage(
                    child: RoomsPage(),
                  );
                },
              ),
              GoRoute(
                parentNavigatorKey: Utils.tabNav,
                path: DevicesPage.route,
                pageBuilder: (context, state) {
                  return const NoTransitionPage(
                    child: DevicesPage(),
                  );
                },
              ),
              GoRoute(
                parentNavigatorKey: Utils.tabNav,
                path: SettingsPage.route,
                pageBuilder: (context, state) {
                  return const NoTransitionPage(
                    child: SettingsPage(),
                  );
                },
              ),
            ]),
      ]);
}

Provider

final bottomBarMenuProvider =
    StateNotifierProvider<BottomBarMenuViewModel, List<BottomBarMenuItemModel>>(
        (ref) {
  final navItems = ref.read(bottomBarRepositoryProvider).getBottomBarNavItems();
  return BottomBarMenuViewModel(navItems, ref);
});

final bottomBarRepositoryProvider = Provider((ref) {
  return BottomMenuBarRepository();
});

Menu Repository

class BottomMenuBarRepository {
  List<BottomBarMenuItemModel> getBottomBarNavItems() {
    return const [
      BottomBarMenuItemModel(
        iconOption: Icons.home,
        route: HomePage.route,
        isSelected: true,
      ),
      BottomBarMenuItemModel(
        iconOption: Icons.door_back_door,
        route: RoomsPage.route,
      ),
      BottomBarMenuItemModel(
        iconOption: Icons.devices,
        route: DevicesPage.route,
      ),
      BottomBarMenuItemModel(
        iconOption: Icons.build,
        route: SettingsPage.route,
      )
    ];
  }
}

Bottom Menu

class HomeAutomationBottomMenu extends ConsumerWidget {
  const HomeAutomationBottomMenu({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final menuItems = ref.watch(bottomBarMenuProvider);

    return Container(
      padding: AppThemeStyles.xsmallPadding,
      child: Flex(
          direction: Axis.horizontal,
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: menuItems.map((e) {
            return Container(
              margin: const EdgeInsets.only(
                bottom: AppThemeStyles.smallSize,
              ),
              child: IconButton(
                  onPressed: () {
                    ref.read(bottomBarMenuProvider.notifier).selectedItem(e);
                    debugPrint(e.route);
                  },
                  icon: Icon(
                    e.iconOption,
                    color: e.isSelected
                        ? Theme.of(context).colorScheme.primary
                        : Theme.of(context).iconTheme.color,
                  )),
            );
          }).toList()),
    );
  }
}

Main Page

class MainPage extends StatelessWidget {
  final Widget child;
  const MainPage({super.key, required this.child});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Flex(
        direction: Axis.vertical,
        children: [
          Expanded(
            child: SafeArea(child: child),
          ),
          const HomeAutomationBottomMenu()
        ],
      ),
    );
  }
}

menu

Whenever you call your bottomBarMenuProvider it calls the bottomBarRepositoryProvider and since you’re not mutating your repository your getBottomBarNavItems is always returning the default values which sets the home as selected by default.


You can verify that by setting another tab to default. The same issue will occur but with that other tab.

1 Like

I do update my GoRouter to the selected route in

class BottomBarViewModel extends StateNotifier<List<BottomBarMenuItemModel>> {
  final Ref ref;
  BottomBarViewModel(super.state, this.ref);

  void selectedItem(BottomBarMenuItemModel selectedItem) {
    state = [
      for (var item in state) item.copyWith(isSelected: selectedItem == item)
    ];

    GoRouter.of(Utils.tabNav.currentContext!).go(selectedItem.route);
  }

}

Sorry I did omit this from the first post

All this code above works 100%.

GoRouter :white_check_mark:
Providers :white_check_mark:
Repository :white_check_mark:
Menu :white_check_mark:
Main Page :white_check_mark:

Where I called the GoRouter to go the Main Page, that is where the issue were “addPersistentFrameCallback” instead of “addPostFrameCallback”, my intension were the Post Call back, but you know how we are, first letter of the Cemal we only read.

  final List<FrameCallback> _persistentCallbacks = <FrameCallback>[];

  /// Adds a persistent frame callback.
  ///
  /// Persistent callbacks are called after transient
  /// (non-persistent) frame callbacks.
  ///
  /// Does *not* request a new frame. Conceptually, persistent frame
  /// callbacks are observers of "begin frame" events. Since they are
  /// executed after the transient frame callbacks they can drive the
  /// rendering pipeline.
  ///
  /// Persistent frame callbacks cannot be unregistered. Once registered, they
  /// are called for every frame for the lifetime of the application.
  ///
  /// See also:
  ///
  ///  * [WidgetsBinding.drawFrame], which explains the phases of each frame
  ///    for those apps that use Flutter widgets (and where persistent frame
  ///    callbacks fit into those phases).
  final List<FrameCallback> _postFrameCallbacks = <FrameCallback>[];

  /// Schedule a callback for the end of this frame.
  ///
  /// The provided callback is run immediately after a frame, just after the
  /// persistent frame callbacks (which is when the main rendering pipeline has
  /// been flushed).
  ///
  /// This method does *not* request a new frame. If a frame is already in
  /// progress and the execution of post-frame callbacks has not yet begun, then
  /// the registered callback is executed at the end of the current frame.
  /// Otherwise, the registered callback is executed after the next frame
  /// (whenever that may be, if ever).
  ///
  /// The callbacks are executed in the order in which they have been
  /// added.
  ///
  /// Post-frame callbacks cannot be unregistered. They are called exactly once.
  ///
  /// In debug mode, if [debugTracePostFrameCallbacks] is set to true, then the
  /// registered callback will show up in the timeline events chart, which can
  /// be viewed in [DevTools](https://docs.flutter.dev/tools/devtools).
  /// In that case, the `debugLabel` argument specifies the name of the callback
  /// as it will appear in the timeline. In profile and release builds,
  /// post-frame are never traced, and the `debugLabel` argument is ignored.
  ///
  /// See also:
  ///
  ///  * [scheduleFrameCallback], which registers a callback for the start of
  ///    the next frame.
  ///  * [WidgetsBinding.drawFrame], which explains the phases of each frame
  ///    for those apps that use Flutter widgets (and where post frame
  ///    callbacks fit into those phases).