Code Monkey home page Code Monkey logo

nested-navigation-demo-flutter's Introduction

Flutter BottomNavigationBar with Multiple Navigators

This is the source code for my article:

Preview

In this example each tab has its own navigation stack. This is so that we don’t lose the navigation history when switching tabs.

This is a very common use case for a lot of apps.

How is it built?

  • Create an app with a Scaffold and a BottomNavigationBar.
  • In the Scaffold body, create a Stack with one child for each tab.
  • Each child is an Offstage widget with a child Navigator.
  • Don't forget to handle Android back navigation with WillPopScope.

Read the full story on my article:

Credits

  • Brian Egan: for suggesting to use Stack + Offstage & Navigator widgets.

nested-navigation-demo-flutter's People

Contributors

bizz84 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

nested-navigation-demo-flutter's Issues

Another example?

Hello, i'm in trouble to understand how to implement this method to change page every tab is pressed. did anyone have other example which the navbar can move to different page class? sorry for my bad english

Issue: Changing one tab triggers the build method of other tabs.

I notice that changing 1 tab will trigger the build method of other tabs which is very bad for performance I think. For example, In ColorsListPage class, I added the log line in the build method, and when I change the tab I can see the build method of other tabs are called:

@override
  Widget build(BuildContext context) {
    log('Rebuild: $title.');
    return Scaffold(
        appBar: AppBar(
          title: Text(
            title,
          ),
          backgroundColor: color,
        ),
        body: Container(
          color: Colors.white,
          child: _buildList(),
        ));
  }

[Feature] Transitioning between pages using fade in/out animation

Hi Andrea,
I really loved this bottomNavigationBar implementation because it persists the states of the pages.

I tried myself very hard but could not get fade in/out animations while changing Offstage page one to another to work.

I believe it would look so much nicer to have a very quick and simple animation transitioning between the pages.

Also while looking at Offstage's documentation it says:

/// * [Visibility], which can hide a child more efficiently (albeit less
/// subtly).
/// * [TickerMode], which can be used to disable animations in a subtree.

Do you think Offstage would not be performant if our navigation stacks are deep?

Thanks.

FAILURE: Build failed with an exception. (with solution to solve)

Hi Andrea,

When I check this out and try to run the project, I get the following error:

FAILURE: Build failed with an exception.

  • What went wrong:
    A problem occurred evaluating root project 'android'.

A problem occurred configuring project ':app'.
No toolchains found in the NDK toolchains folder for ABI with prefix: mips64el-linux-android


Solution: delete the android and ios folder and recreate using:
flutter create --template=app .

You can find the complete error message and flutter doctor here below:

Keep up the good work!
Regards,
Sander Roest

complete error:
Launching lib/main.dart on Android SDK built for x86 64 in debug mode...
Initializing gradle...
Resolving dependencies...

  • Error running Gradle:
    ProcessException: Process "/Users/johannesroest/repos/nested_navigation_demo_flutter/android/gradlew" exited abnormally:

FAILURE: Build failed with an exception.

  • Where:
    Build file '/Users/johannesroest/repos/nested_navigation_demo_flutter/android/build.gradle' line: 26

  • What went wrong:
    A problem occurred evaluating root project 'android'.

A problem occurred configuring project ':app'.
No toolchains found in the NDK toolchains folder for ABI with prefix: mips64el-linux-android

  • Try:
    Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

  • Get more help at https://help.gradle.org

BUILD FAILED in 0s
Command: /Users/johannesroest/repos/nested_navigation_demo_flutter/android/gradlew app:properties

Finished with error: Please review your Gradle project setup in the android/ folder.

Flutter doctor:
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, v1.7.8+hotfix.4, on Mac OS X 10.14.6 18G87, locale nl-NL)
[✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3)
[✓] Xcode - develop for iOS and macOS (Xcode 10.3)
[✓] iOS tools - develop for iOS devices
[✓] Android Studio (version 3.5)
[✓] VS Code (version 1.37.1)
[✓] Connected device (2 available)

• No issues found!

How to get parent navigator in flutter not an ancestor?

I am facing getting parent navigator in flutter and I am trying this approach.

In login screen, when authentication is successful, app navigates to Home screen which has bottom navigator. bottom navigator has 3 tabs and each tab has multiple screen as child. To keep navigator independent between 3 tabs, I defined navigatorKeys for each tab.

And to navigate to detail screen of each tab, I am using this code snippet.

void _push(BuildContext context, {required String routeName}) {
    final routeBuilders = _routeBuilders(context);

    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => routeBuilders[routeName]!(context),
      ),
    );
}

The problem is navigated screen overlaps bottom tab but my intention was to keep bottom navigation bar. I think Navigator gets the ancestor navigator which is parent of both login screen and home screen and that's why child screen of tab navigator overlays bottom navigation bar. I am not a veteran at Flutter so have no idea how to handle this at the moment. It would be appreciated if someone could help me with this.

Thank you!

Skip maintaining the state on each tab screen.

I would like to know that how can i skip maintaining the state of the visited tab screen. What i mean is if i am on the first tab and i visit a nested route within that tab and then switch to the second tab. Now coming back to the first tab should always take me to the initial screen rather then the nested screen. How do i do that?

A few warnings...

Built build\app\outputs\apk\debug\app-debug.apk.
Flutter is taking longer than expected to report its views. Still trying...
W/tiondemoflutte(  569): Accessing hidden method Landroid/view/accessibility/AccessibilityNodeInfo;->getSourceNodeId()J (greylist, reflection, allowed)
W/tiondemoflutte(  569): Accessing hidden method Landroid/view/accessibility/AccessibilityRecord;->getSourceNodeId()J (greylist, reflection, allowed)
W/tiondemoflutte(  569): Accessing hidden field Landroid/view/accessibility/AccessibilityNodeInfo;->mChildNodeIds:Landroid/util/LongArray; (greylist, reflection, allowed)
W/tiondemoflutte(  569): Accessing hidden method Landroid/util/LongArray;->get(I)J (greylist, reflection, allowed)
W/Gralloc3(  569): mapper 3.x is not supported

Just thought I would let you know that this is throwing a few warnings

Instead of list i need to select from popup

Hi @bizz84

I have a page called menu1 which is my home screen. I have bottom bar in which i have 5 icons. If i select first icon, 4 menus will open . If i select menu 1, it should display menu1screen and in that case bottombar should remain constant.Similarly if i change other menus in popup, it should display it respective screens. How can i acheive this scenario?

Any help is really appreciated!!

popup

Remove Bottom Navigation Bar on certain routes/pages

Hi! Thank you for the great plugin.

Is it possible to remove the bottom navigation on certain pages?
i.e. I want to open some fullscreen pages without the bottom navigation bar and return to pages with the bottom navigation bar once the fullscreen page finished (like Android's startActivityForResult).

Thank you!

Solution is not performant

Hi there Andrea - it would be great to get your view on the questions I am raising.

I see that this example is an updated version based on what you originally wrote in Medium in 2018 :

https://medium.com/coding-with-flutter/flutter-case-study-multiple-navigators-with-bottomnavigationbar-90eb6caa6dbf.

The new updated version has been changed to accommodate GoRouter etc. I also notice that you have further explained this (and performance issues with your original approach) here :

https://codewithandrea.com/articles/flutter-bottom-navigation-bar-nested-routes-gorouter-beamer/

However, both approaches still cause unnecessary rebuilds for the entire widget tree. This happens when :

  • Any tab is pressed (All TABS get rebuilt regardless of which tab is pressed)
  • Testing in a Tablet, Orientation changes causes ALL Tabs to get rebuilt.
  • Keyboard input on 1 tab causes ALL tabs to get rebuilt

Furthermore, I have noticed considerable performance issues using OffStage - presumably this is because when the widget comes back 'onstage' based on it being the current tab selected, it gets completely rebuilt.

I see no reason why selecting 1 tab should rebuild ALL tabs so consequently - is a limitation of your use of GoRouter/Beamer or Flutter itself?

Hot reload not working

Hi,
Thanks for the amazing work. I encountered a problem, where hot reload not working for the screens inside the navigator. Here is my code

class App extends StatefulWidget {
  @override
  _AppState createState() => _AppState();
}

class _AppState extends State<App> {
  final Map<TabItem, Map<String, Object>> _navItems = {
    TabItem.CALCULATOR: {
      'icon': FlutterIcons.calc,
      'title': 'Calculator',
      'pageTitle': 'Stock Calculator',
      'key': GlobalKey<NavigatorState>()
    },
    TabItem.ACCOUNTS: {
      'icon': Icons.insert_chart,
      'title': 'Accounts',
      'pageTitle': 'Accounts',
      'key': GlobalKey<NavigatorState>()
    },
    TabItem.SETTINGS: {
      'icon': Icons.settings,
      'title': 'Settings',
      'pageTitle': 'Settings',
      'key': GlobalKey<NavigatorState>()
    },
  };

  TabItem _currentTab = TabItem.CALCULATOR;

  Widget _renderPage() {
    Map<String, Object> item = _navItems[_currentTab];
    var key = item['key'] as GlobalKey<NavigatorState>;
    var pageTitle = item['pageTitle'] as String;
    switch (_currentTab) {
      case TabItem.CALCULATOR:
        return HomePage(
          key: key,
          title: pageTitle,
        );
        break;
      case TabItem.ACCOUNTS:
        return AccountsPage(
          key: key,
          title: pageTitle,
        );
        break;
      case TabItem.SETTINGS:
        return SettingsPageNavigator(
          navigatorKey: key,
          tabItem: TabItem.SETTINGS,
        );
        break;
      default:
        return HomePage(
          key: key,
          title: pageTitle,
        );
    }
  }

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () async {
        bool flag = true;
        try {
          if (TabItem.SETTINGS == _currentTab) {
            final isFirstRouteInCurrentTab = await (_navItems[_currentTab]
                    ['key'] as GlobalKey<NavigatorState>)
                .currentState
                .maybePop();
            if (isFirstRouteInCurrentTab) {
              _onTabSelect(TabItem.SETTINGS);
              flag = false;
            }
          }
        } catch (e) {}
        return flag;
      },
      child: Scaffold(
        backgroundColor: Colors.red,
        body: _renderPage(),
        bottomNavigationBar: BottomNavigationBar(
          backgroundColor: Theme.of(context).primaryColor,
          unselectedItemColor: Colors.white54,
          selectedItemColor: Colors.white,
          onTap: (index) => _onTabSelect(TabItem.values[index]),
          currentIndex: _currentTab.index,
          elevation: 5,
          type: BottomNavigationBarType.fixed,
          items: [
            _buildNavItem(tabItem: TabItem.CALCULATOR),
            _buildNavItem(tabItem: TabItem.ACCOUNTS),
            _buildNavItem(tabItem: TabItem.SETTINGS),
          ],
        ),
      ),
    );
  }

  _onTabSelect(TabItem tab) {
    setState(() {
      _currentTab = tab;
    });
  }

  BottomNavigationBarItem _buildNavItem({TabItem tabItem}) {
    return BottomNavigationBarItem(
        icon: Icon(_navItems[tabItem]['icon']),
        title: Text(_navItems[tabItem]['title']));
  }
}

Help : GlobalKey<NavigatorState> was used multiple times error.

Thanks for great example @bizz84 . I used in my projects without any problem so far.
But i stuck in one point. If you can help, i really appreciate it.
I wanted to integrate Firebase Analytics in my project to logging events.

static FirebaseAnalytics analytics = FirebaseAnalytics(); static FirebaseAnalyticsObserver observer = FirebaseAnalyticsObserver(analytics: analytics);

In MaterialApp i put observer, I reliazed that analytics not logging. If i remove tabbar completely it works. Then i found that if i pass observer param to Navigator page you defined, it works for first page but when i switch between tabs i get GlobalKey used multiple times error.

How can we call GlobalKey once ?

Screen Shot 2019-06-09 at 08 01 21

How to navigate other routes from nested screen ?

There are some screen page under the root of app,
and they are not in tabs. Such as Login Screen.

I Try to try to navigate to these screen page by the way: Navigator.of(context).pushNamed('/login'); but it cannot.

Navigate with rouut navigator

Im some cases, like playing a full screen video the bottom bar its going to be visible.

I want to do a Navigate.push in the default root navigator, and dont navigate in the tab.
Is it possible? Any idea?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.