{"id":8012,"date":"2023-05-31T11:49:38","date_gmt":"2023-05-31T11:49:38","guid":{"rendered":"https:\/\/www.bacancytechnology.com\/qanda\/?p=8012"},"modified":"2024-12-16T11:26:21","modified_gmt":"2024-12-16T11:26:21","slug":"keep-bottomnavigationbar-when-push-to-new-screen-with-navigator","status":"publish","type":"post","link":"https:\/\/www.bacancytechnology.com\/qanda\/flutter\/keep-bottomnavigationbar-when-push-to-new-screen-with-navigator","title":{"rendered":"Problem Statement &#8211; Bottom nav Flutter Won\u2019t Load Until Open"},"content":{"rendered":"<p>In this example, each tab has its own navigation stack. This is so we don\u2019t lose the navigation history when switching tabs.<br \/>\nThis is a very common use case for a lot of apps.<\/p>\n<p><strong>How is it built?<\/strong><br \/>\n<strong>&#8211; Create an app with a Scaffold and a BottomNavigationBar.<\/strong><\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\">@override\r\n  Widget build(BuildContext context) {\r\n    return Scaffold(\r\n      body: _buildBody(),\r\n      bottomNavigationBar: BottomNavigation(\r\n        currentTab: _currentTab,\r\n        onSelectTab: _selectTab,\r\n      ),\r\n    );\r\n  }<\/pre>\n<p><strong>&#8211; In the Scaffold body, create a Stack with one child for each tab, and don&#8217;t forget to handle Android back navigation with WillPopScope.<\/strong><\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\">@override\r\n  Widget build(BuildContext context) {\r\n    return WillPopScope(\r\n      onWillPop: () async {\r\n        final isFirstRouteInCurrentTab =\r\n            !await _navigatorKeys[_currentTab]!.currentState!.maybePop();\r\n        if (isFirstRouteInCurrentTab) {\r\n          \/\/ if not on the 'main' tab\r\n          if (_currentTab != TabItem.red) {\r\n            \/\/ select 'main' tab\r\n            _selectTab(TabItem.red);\r\n            \/\/ back button handled by app\r\n            return false;\r\n          }\r\n        }\r\n        \/\/ let system handle back button if we're on the first route\r\n        return isFirstRouteInCurrentTab;\r\n      },\r\n      child: Scaffold(\r\n        body: Stack(children: &lt;Widget&gt;[\r\n          _buildOffstageNavigator(TabItem.red),\r\n          _buildOffstageNavigator(TabItem.green),\r\n          _buildOffstageNavigator(TabItem.blue),\r\n        ]),\r\n        bottomNavigationBar: BottomNavigation(\r\n          currentTab: _currentTab,\r\n          onSelectTab: _selectTab,\r\n        ),\r\n      ),\r\n    );\r\n  }<\/pre>\n<p><strong>&#8211; Create each child with an Offstage widget with a child Navigator.<\/strong><\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\">final navigatorKey = GlobalKey&lt;NavigatorState&gt;();\r\n\r\n  @override\r\n  Widget build(BuildContext context) {\r\n    return Scaffold(\r\n      body: TabNavigator(\r\n        navigatorKey: navigatorKey,\r\n        tabItem: currentTab,\r\n      ),\r\n      bottomNavigationBar: BottomNavigation(\r\n        currentTab: currentTab,\r\n        onSelectTab: _selectTab,\r\n      ),\r\n    );\r\n  }<\/pre>\n<p><strong>## How does the _push() method work?<\/strong><\/p>\n<p>MaterialPageRoute takes care of creating a new route to be pushed<br \/>\nNavigator.of(context) finds a Navigator above in the widget tree and uses it to push the new route.<br \/>\nYou may wonder, where does the Navigator widget come from?<br \/>\nWe haven&#8217;t created one ourselves and the parent of our App class is the MaterialApp at the root of the widget tree.<br \/>\nAs it turns out, MaterialApp creates its own Navigator internally.<br \/>\nHowever, if we just use Navigator.of(context) to push the new route, something unexpected happens.<br \/>\nThe whole BottomNavigationBar and its contents slide away as the new page is presented.<\/p>\n<p><strong>## Ok Navigator, show me what you can do?<\/strong><\/p>\n<p>The solution is to wrap the body of our Scaffold object with a new Navigator.<br \/>\nHow does this work?<\/p>\n<p>In step 1, we define two route names: \/ and \/detail.<\/p>\n<p>In step 2, we define the constructor for TabNavigator. This takes a navigatorKey and a tabItem.<\/p>\n<p>Note that navigatorKey has the type GlobalKey. We need this to uniquely identify the navigator across the entire app (read more about GlobalKey here).<\/p>\n<p>In step 3, we define a _routeBuilders method, which associates a WidgetBuilder to each of the two routes we have defined. We&#8217;ll look at the ColorsListPage and ColorDetailPage in a second.<\/p>\n<p>In step 4, we implement the build() method, which returns a new Navigator object. This takes a key and an initialRoute parameter.<\/p>\n<p>It also has an onGenerateRoute method, which is called every time a route needs to be generated. This uses the _routeBuilders() method we have defined above.<\/p>\n<p>In step 5, we define a _push() method used to push a detailed route with a ColorDetailPage.<\/p>\n<p><strong>\/\/ 1<\/strong><\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\">class TabNavigatorRoutes {\r\n  static const String root = '\/';\r\n  static const String detail = '\/detail';\r\n}\r\n\r\n<\/pre>\n<p><strong>\/\/ 2<\/strong><\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\">class TabNavigator extends StatelessWidget {\r\n  TabNavigator({this.navigatorKey, this.tabItem});\r\n  final GlobalKey&lt;NavigatorState&gt; navigatorKey;\r\n  final TabItem tabItem;<\/pre>\n<p><strong>\/\/ 3<\/strong><\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\">Map&lt;String, WidgetBuilder&gt; _routeBuilders(BuildContext context,\r\n    {int materialIndex: 500}) {\r\n  return {\r\n    TabNavigatorRoutes.root: (context) =&gt; ColorsListPage(\r\n          color: TabHelper.color(tabItem),\r\n          title: TabHelper.description(tabItem),\r\n          onPush: (materialIndex) =&gt;\r\n              _push(context, materialIndex: materialIndex),\r\n        ),\r\n    TabNavigatorRoutes.detail: (context) =&gt; ColorDetailPage(\r\n          color: TabHelper.color(tabItem),\r\n          title: TabHelper.description(tabItem),\r\n          materialIndex: materialIndex,\r\n        ),\r\n  };\r\n}<\/pre>\n<p><strong>\/\/ 4<\/strong><\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\">@override\r\n  Widget build(BuildContext context) {\r\n    final routeBuilders = _routeBuilders(context);\r\n    return Navigator(\r\n      key: navigatorKey,\r\n      initialRoute: TabNavigatorRoutes.root,\r\n      onGenerateRoute: (routeSettings) {\r\n        return MaterialPageRoute(\r\n          builder: (context) =&gt; routeBuilders[routeSettings.name](context),\r\n        );\r\n      },\r\n    );\r\n  }<\/pre>\n<p><strong>\/\/ 5<\/strong><\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\">void _push(BuildContext context, {int materialIndex: 500}) {\r\n    var routeBuilders = _routeBuilders(context, materialIndex: materialIndex);\r\n\r\n    Navigator.push(\r\n      context,\r\n      MaterialPageRoute(\r\n        builder: (context) =&gt; routeBuilders[TabNavigatorRoutes.detail](context),\r\n      ),\r\n    );\r\n  }\r\n}<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>In this example, each tab has its own navigation stack. This is so we don\u2019t lose the navigation history when switching tabs. This is a very common use case for a lot of apps. How is it built? &#8211; Create an app with a Scaffold and a BottomNavigationBar. @override Widget build(BuildContext context) { return Scaffold( [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":8295,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"inline_featured_image":false,"footnotes":""},"categories":[15],"tags":[],"class_list":["post-8012","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-flutter"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.bacancytechnology.com\/qanda\/wp-json\/wp\/v2\/posts\/8012"}],"collection":[{"href":"https:\/\/www.bacancytechnology.com\/qanda\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.bacancytechnology.com\/qanda\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.bacancytechnology.com\/qanda\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.bacancytechnology.com\/qanda\/wp-json\/wp\/v2\/comments?post=8012"}],"version-history":[{"count":4,"href":"https:\/\/www.bacancytechnology.com\/qanda\/wp-json\/wp\/v2\/posts\/8012\/revisions"}],"predecessor-version":[{"id":8014,"href":"https:\/\/www.bacancytechnology.com\/qanda\/wp-json\/wp\/v2\/posts\/8012\/revisions\/8014"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.bacancytechnology.com\/qanda\/wp-json\/wp\/v2\/media\/8295"}],"wp:attachment":[{"href":"https:\/\/www.bacancytechnology.com\/qanda\/wp-json\/wp\/v2\/media?parent=8012"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.bacancytechnology.com\/qanda\/wp-json\/wp\/v2\/categories?post=8012"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.bacancytechnology.com\/qanda\/wp-json\/wp\/v2\/tags?post=8012"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}