title: Flutter web Bottom Navigation Bar with url updating and hyperlink support
body: <p>I have been searching around for ages online for an example of a flutter app that supports the following:</p>
  <ol>
  <li>Persistent bottom navigation bar</li>
  <li>Navigator support for each bottom navigation bar item that persists state</li>
  <li>Navigators update the URL in flutter web on navigation</li>
  <li>Navigators support hyperlinking to a specific page in flutter web. For example if a user types <a href="http://www.myappdomain.com/#/profile" rel="noreferrer">www.myappdomain.com/#/profile</a> they get navigated to the profile page. Or they type <a href="http://www.myappdomain.com/#/profile?id=123" rel="noreferrer">www.myappdomain.com/#/profile?id=123</a> they get navigated to the profile page for user 123.</li>
  </ol>
  <p>I've been able to achieve points 1 and 2 with the following code:</p>
  <pre class="lang-dart prettyprint-override"><code>import 'package:flutter/material.dart';
  
  void main() {
    runApp(BaseApp());
  }
  
  class BaseApp extends StatefulWidget {
    @override
    _BaseAppState createState() =&gt; _BaseAppState();
  }
  
  class _BaseAppState extends State&lt;BaseApp&gt; {
    // define all navigation tabs for user
    static List&lt;NavigationTabModel&gt; _navigationTabs = [
      NavigationTabModel(
        title: 'Home',
        icon: Icons.home,
        url: '/home',
        navigatorKey: GlobalKey&lt;NavigatorState&gt;(),
      ),
      NavigationTabModel(
        title: 'Search',
        icon: Icons.search,
        url: '/search',
        navigatorKey: GlobalKey&lt;NavigatorState&gt;(),
      ),
      NavigationTabModel(
        title: 'Profile',
        icon: Icons.person,
        url: '/profile',
        navigatorKey: GlobalKey&lt;NavigatorState&gt;(),
      ),
    ];
  
    // route generator used to build all material page routes
    RouteGenerator _routeGenerator = RouteGenerator();
  
    // set the current tab to the home page
    int _currentIndex = 0;
  
    void _select(int index) {
      if (index == _currentIndex) {
        // case 1 - if user presses on currently selected tab
        // pop to first route - i.e. ensure no routes are over laid on top of the current route
        _navigationTabs[_currentIndex]
            .navigatorKey
            .currentState
            .popUntil((route) {
          return route.isFirst;
        });
      } else {
        // case 2 - user selects any other tab
        // rebuild application state with the newly selected navigation tab
        setState(() {
          _currentIndex = index;
        });
      }
    }
  
    /// generate a list of navigators that will have their state persisted in an
    /// indexed stack.
    List&lt;Widget&gt; _getPersistantStack() {
      return _navigationTabs.map((tab) {
        return WillPopScope(
          onWillPop: () async {
            return !await tab.navigatorKey.currentState.maybePop();
          },
          child: Navigator(
            key: tab.navigatorKey,
            initialRoute: tab.url,
            onGenerateRoute: _routeGenerator.generateRoute,
          ),
        );
      }).toList();
    }
  
    @override
    Widget build(BuildContext context) {
      /// ********************* HOLD POINT *********************
      /// MaterialApp contains our top-level Navigator. Top level navigator is
      /// required to enable navigation via urls in flutter web. Likely that this
      /// section requires refractoring in some way to enable url updates from the
      /// nested navigators and hyperlinking from web browsers to specific pages
      return MaterialApp(
        debugShowCheckedModeBanner: false,
        home: Scaffold(
          body: SafeArea(
            child: IndexedStack(
              children: _getPersistantStack(),
              index: _currentIndex,
            ),
          ),
          bottomNavigationBar: BottomNavigationBar(
            items: _navigationTabs.map((tab) {
              return BottomNavigationBarItem(
                label: tab.title,
                icon: Icon(tab.icon),
              );
            }).toList(),
            onTap: (int index) {
              _select(index);
            },
            currentIndex: _currentIndex,
            type: BottomNavigationBarType.fixed,
            // hide titles on navigation bar
            showSelectedLabels: false,
            showUnselectedLabels: false,
          ),
        ),
      );
    }
  }
  
  class NavigationTabModel {
    final String title;
    final IconData icon;
    final String url;
    final GlobalKey&lt;NavigatorState&gt; navigatorKey;
  
    NavigationTabModel({
      this.title,
      this.icon,
      this.url,
      this.navigatorKey,
    });
  }
  
  class RouteGenerator {
    Route&lt;dynamic&gt; generateRoute(RouteSettings settings) {
      // Widget builder (function that returns a widget) to construct the route page
      WidgetBuilder builder;
  
      // build different route (page) based on the route passed to the navigator
      switch (settings.name) {
        case '/home':
          builder = (BuildContext context) {
            return SamplePage(name: 'home');
          };
          break;
        case '/search':
          builder = (BuildContext context) {
            return SamplePage(name: 'search');
          };
          break;
        case '/profile':
          builder = (BuildContext context) {
            return SamplePage(name: 'profile');
          };
          break;
        case '/':
          builder = null;
          break;
        default:
          // If there is no such named route in the switch statement
          builder = (BuildContext context) {
            return SamplePage();
          };
      }
      // prevent page being added to default '/' route
      if (builder == null) {
        return null;
      }
      return MaterialPageRoute(
        builder: builder,
        settings: settings,
      );
    }
  }
  
  class SamplePage extends StatelessWidget {
    final String name;
  
    SamplePage({
      this.name,
    });
  
    @override
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(
          title: Text('$name'),
        ),
        body: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            children: [
              RaisedButton(
                child: Text('push new route'),
                onPressed: () {
                  Navigator.of(context).pushNamed('/$name');
                },
              ),
              SizedBox(
                height: 16,
              ),
              Expanded(
                child: ListView.builder(
                  itemCount: 100,
                  itemBuilder: (context, index) {
                    return Card(
                      child: Text(
                        index.toString(),
                      ),
                    );
                  },
                ),
              ),
            ],
          ),
        ),
      );
    }
  }
  </code></pre>
  <p>However I cannot figure out how to update this app to achieve points 3 and 4. Does anyone know how this can be achieved?</p>