Flutter - Pushing into a StatefulShellRoute

53 views Asked by At

I want to implement the following navigation:

GoRouter
├─ StatefulShellRoute-1(root of the app)
│  ├─ GoRoute(/tab-1)
│  ├─ GoRoute(/tab-2)
│  ├─ GoRoute(/tab-3)
│  ├─ GoRoute(/tab-4)
│  ├─ GoRoute(/tab-5)
├─ StatefulShellRoute-2(a menu can be opened from anywhere in the app)
│  ├─ GoRoute(/menu/tab-1)
│  ├─ GoRoute(/menu/tab-2)
│  ├─ GoRoute(/menu/tab-3)

Navigation seems fine at first but I have a problem where if I push /menu/tab-1 from /tab-n, then I navigate with goBranch to /menu/tab-2, I cannot pop back to /tab-n as the navigator complains there is nothing to pop.

Full code:

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

void main() {
  runApp(const MainApp());
}

abstract class Routes {
  static const tab1 = "/tab-1";
  static const tab2 = "/tab-2";
  static const tab3 = "/tab-3";
  static const tab4 = "/tab-4";
  static const tab5 = "/tab-5";

  static const test = "/test";

  static const menuTab1 = "/menu/tab-1";
  static const menuTab2 = "/menu/tab-2";
  static const menuTab3 = "/menu/tab-3";
}

abstract class AppRouter {
  static final parentNavigatorKey = GlobalKey<NavigatorState>();
  static final tab1NavigatorKey = GlobalKey<NavigatorState>();
  static final tab2NavigatorKey = GlobalKey<NavigatorState>();
  static final tab3NavigatorKey = GlobalKey<NavigatorState>();
  static final tab4NavigatorKey = GlobalKey<NavigatorState>();
  static final tab5NavigatorKey = GlobalKey<NavigatorState>();

  static final menuTab1NavigatorKey = GlobalKey<NavigatorState>();
  static final menuTab2NavigatorKey = GlobalKey<NavigatorState>();
  static final menuTab3NavigatorKey = GlobalKey<NavigatorState>();

  static Page getPage({
    required Widget child,
    required GoRouterState state,
  }) {
    return MaterialPage(
      key: state.pageKey,
      child: child,
    );
  }

  static GoRouter router = GoRouter(
    navigatorKey: parentNavigatorKey,
    initialLocation: Routes.tab1,
    routes: [
      StatefulShellRoute.indexedStack(
        parentNavigatorKey: parentNavigatorKey,
        pageBuilder: (
          BuildContext context,
          GoRouterState state,
          StatefulNavigationShell navigationShell,
        ) {
          return getPage(
            child: RootNavigation(
              navigationShell: navigationShell,
            ),
            state: state,
          );
        },
        branches: [
          StatefulShellBranch(
            navigatorKey: tab1NavigatorKey,
            routes: [
              GoRoute(
                path: Routes.tab1,
                pageBuilder: (context, GoRouterState state) {
                  return getPage(
                    child: const Center(
                      child: Text("Tab 1 content"),
                    ),
                    state: state,
                  );
                },
              ),
            ],
          ),
          StatefulShellBranch(
            navigatorKey: tab2NavigatorKey,
            routes: [
              GoRoute(
                path: Routes.tab2,
                pageBuilder: (context, state) {
                  return getPage(
                    child: const Center(
                      child: Text("Tab 2 content"),
                    ),
                    state: state,
                  );
                },
              ),
            ],
          ),
          StatefulShellBranch(
            navigatorKey: tab3NavigatorKey,
            routes: [
              GoRoute(
                path: Routes.tab3,
                pageBuilder: (context, state) {
                  return getPage(
                    child: const Center(
                      child: Text("Tab 3 content"),
                    ),
                    state: state,
                  );
                },
              ),
            ],
          ),
          StatefulShellBranch(
            navigatorKey: tab4NavigatorKey,
            routes: [
              GoRoute(
                path: Routes.tab4,
                pageBuilder: (context, state) {
                  return getPage(
                    child: const Center(
                      child: Text("Tab 4 content"),
                    ),
                    state: state,
                  );
                },
              ),
            ],
          ),
          StatefulShellBranch(
            navigatorKey: tab5NavigatorKey,
            routes: [
              GoRoute(
                path: Routes.tab5,
                pageBuilder: (context, state) {
                  return getPage(
                    child: const Center(
                      child: Text("Tab 5 content"),
                    ),
                    state: state,
                  );
                },
              ),
            ],
          ),
        ],
      ),
      StatefulShellRoute.indexedStack(
        parentNavigatorKey: parentNavigatorKey,
        pageBuilder: (
          BuildContext context,
          GoRouterState state,
          StatefulNavigationShell navigationShell,
        ) {
          return getPage(
            child: Menu(
              shell: navigationShell,
            ),
            state: state,
          );
        },
        branches: [
          StatefulShellBranch(
            navigatorKey: menuTab1NavigatorKey,
            routes: [
              GoRoute(
                path: Routes.menuTab1,
                pageBuilder: (context, GoRouterState state) {
                  return getPage(
                    child: Center(
                      child: const Text("Menu tab 1"),
                    ),
                    state: state,
                  );
                },
              ),
            ],
          ),
          StatefulShellBranch(
            navigatorKey: menuTab2NavigatorKey,
            routes: [
              GoRoute(
                path: Routes.menuTab2,
                pageBuilder: (context, state) {
                  return getPage(
                    child: Center(
                      child: const Text("Menu tab 2"),
                    ),
                    state: state,
                  );
                },
              ),
            ],
          ),
          StatefulShellBranch(
            navigatorKey: menuTab3NavigatorKey,
            routes: [
              GoRoute(
                path: Routes.menuTab3,
                pageBuilder: (context, state) {
                  return getPage(
                    child: Center(
                      child: const Text("Menu tab 3"),
                    ),
                    state: state,
                  );
                },
              ),
            ],
          ),
        ],
      ),
      GoRoute(
        parentNavigatorKey: parentNavigatorKey,
        path: Routes.test,
        builder: (context, state) {
          return Test();
        },
      ),
    ],
  );
}

class Test extends StatelessWidget {
  const Test({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Hello"),
      ),
      body: Center(
        child: Text("TEST!"),
      ),
    );
  }
}

class MainApp extends StatelessWidget {
  const MainApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerConfig: AppRouter.router,
      debugShowCheckedModeBanner: false,
    );
  }
}

class RootNavigation extends StatelessWidget {
  const RootNavigation({
    required this.navigationShell,
  });

  final StatefulNavigationShell navigationShell;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: SingleChildScrollView(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              navigationShell,
              Container(
                // color: Colors.lightGreen,
                child: Column(
                  children: [
                    Text(
                      "URL Based Navigation",
                      style: Theme.of(context).textTheme.headlineLarge,
                    ),
                    ElevatedButton(
                      onPressed: () {
                        context.go(Routes.tab1);
                      },
                      child: Text("Navigate to: Tab 1"),
                    ),
                    ElevatedButton(
                      onPressed: () {
                        context.go(Routes.tab2);
                      },
                      child: Text("Navigate to: Tab 2"),
                    ),
                    ElevatedButton(
                      onPressed: () {
                        context.go(Routes.tab3);
                      },
                      child: Text("Navigate to: Tab 3"),
                    ),
                    ElevatedButton(
                      onPressed: () {
                        context.go(Routes.tab4);
                      },
                      child: Text("Navigate to: Tab 4"),
                    ),
                    ElevatedButton(
                      onPressed: () {
                        context.go(Routes.tab5);
                      },
                      child: Text("Navigate to: Tab 5"),
                    ),
                    ElevatedButton(
                      onPressed: () {
                        context.push(Routes.menuTab1);
                      },
                      child: Text("Navigate to: Menu 1"),
                    ),
                    ElevatedButton(
                      onPressed: () {
                        context.push(Routes.menuTab2);
                      },
                      child: Text("Navigate to: Menu 2"),
                    ),
                    ElevatedButton(
                      onPressed: () {
                        context.push(Routes.menuTab3);
                      },
                      child: Text("Navigate to: Menu 3"),
                    ),
                    ElevatedButton(
                      onPressed: () {
                        context.push(Routes.test);
                      },
                      child: Text("Navigate to: Test"),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
      bottomNavigationBar: AppNavigationBar(
        child: navigationShell,
      ),
    );
  }
}

class AppNavigationBar extends StatefulWidget {
  const AppNavigationBar({
    super.key,
    required this.child,
  });

  final StatefulNavigationShell child;

  @override
  State<AppNavigationBar> createState() => _AppNavigationBarState();
}

class _AppNavigationBarState extends State<AppNavigationBar> {
  @override
  Widget build(BuildContext context) {
    return NavigationBar(
      selectedIndex: widget.child.currentIndex,
      onDestinationSelected: (index) {
        widget.child.goBranch(
          index,
          initialLocation: index == widget.child.currentIndex,
        );
        setState(() {});
      },
      destinations: const <Widget>[
        NavigationDestination(
          icon: Icon(Icons.hub_outlined),
          selectedIcon: Icon(Icons.hub),
          label: "Tab 1",
        ),
        NavigationDestination(
          icon: Icon(Icons.view_carousel_outlined),
          selectedIcon: Icon(Icons.view_carousel),
          label: "Tab 2",
        ),
        NavigationDestination(
          icon: Icon(Icons.view_carousel_outlined),
          selectedIcon: Icon(Icons.view_carousel),
          label: "Tab 3",
        ),
        NavigationDestination(
          icon: Icon(Icons.view_carousel_outlined),
          selectedIcon: Icon(Icons.view_carousel),
          label: "Tab 4",
        ),
        NavigationDestination(
          icon: Icon(Icons.view_carousel_outlined),
          selectedIcon: Icon(Icons.view_carousel),
          label: "Tab 5",
        ),
      ],
    );
  }
}

enum MenuSegment { MENU1, MENU2, MENU3 }

class Menu extends StatefulWidget {
  Menu({
    required this.shell,
  });

  final StatefulNavigationShell shell;

  @override
  State<Menu> createState() => _MenuState();
}

class _MenuState extends State<Menu> {
  void initState() {
    currentlySelectedMenuSegment =
        MenuSegment.values[widget.shell.currentIndex];

    super.initState();
  }

  late MenuSegment currentlySelectedMenuSegment;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          onPressed: () {
            context.pop();
          },
          icon: Icon(Icons.arrow_back),
        ),
        actions: [
          IconButton(
            onPressed: () {
              context.pop();
            },
            icon: Icon(Icons.close),
          ),
        ],
        backgroundColor: Theme.of(context).colorScheme.background,
        elevation: 0,
      ),
      body: SafeArea(
        bottom: false,
        child: SingleChildScrollView(
          child: Column(
            children: [
              Column(
                children: [
                  const SizedBox(height: 16),
                  const Text(
                    "MENU WELCOME",
                  ),
                  const SizedBox(height: 16),
                  SegmentedButton<MenuSegment>(
                    style: const ButtonStyle(
                      tapTargetSize: MaterialTapTargetSize.shrinkWrap,
                      visualDensity: VisualDensity(
                        horizontal: -1,
                        vertical: -1,
                      ),
                    ),
                    onSelectionChanged: (e) {
                      // onMenuSegmentSelected(e.first);
                      int index = MenuSegment.values.indexOf(e.first);
                      widget.shell.goBranch(
                        index,
                        initialLocation: index == widget.shell.currentIndex,
                      );
                      setState(() {
                        currentlySelectedMenuSegment = e.first;
                      });
                    },
                    segments: const [
                      ButtonSegment<MenuSegment>(
                        value: MenuSegment.MENU1,
                        label: Text("Menu 1"),
                        icon: Icon(Icons.account_circle_outlined),
                      ),
                      ButtonSegment<MenuSegment>(
                        value: MenuSegment.MENU2,
                        label: Text("Menu 2"),
                        icon: Icon(Icons.settings_outlined),
                      ),
                      ButtonSegment<MenuSegment>(
                        value: MenuSegment.MENU3,
                        label: Text("Menu 3"),
                        icon: Icon(Icons.credit_card_outlined),
                      ),
                    ],
                    selected: {currentlySelectedMenuSegment},
                    showSelectedIcon: false,
                  ),
                ],
              ),
              widget.shell,
            ],
          ),
        ),
      ),
    );
  }
}
1

There are 1 answers

1
saraaahhh On

Try using Navigator.of(context).pop(); instead of context.pop();.