Bram
Bram

Reputation: 473

How to persist BottomNavigationBar when using Flutter navigation and routes

I'm making a project with 4 different pages. I use a "BottomNavigationBar" widget to navigate to each page. When an icon in the "BottomNavigationBar" is pressed, I display a different page in the Scaffold body. I don't use any routing so when a user presses back on Android, the app closes. Something I don't want happening.

All the guides I have found reload the whole "Scaffold" when navigating, but I want to only update the "body" property of the "Scaffold" widget. When a Navigation.pop() occurs I again only want the "Scaffold" body to change.

I have found a post around the same issue, but the answer didn't work for me.

Another workaround I can try is making a custom history list, that I then update when pages are changed. Catching OnWillPop event to update the pages when the back button is pressed. I haven't tried this because I feel like there has to be a better way.

The Scaffold widget that displays the page.

  Widget createScaffold() {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: EmptyAppBar(),
      body: _displayedPage,
      bottomNavigationBar: createBottomNavigationbar(),
    );
  }

The BottomNavigationBar widget.

  Widget createBottomNavigationbar() {
    return BottomNavigationBar(
      type: BottomNavigationBarType.fixed,
      currentIndex: _selectedIndex,
      onTap: _onItemTapped,
      items: [
        BottomNavigationBarItem(
          icon: new Icon(Icons.home,
              color: _selectedIndex == 0 ? selectedColor : unselectedColor),
          title: new Text('Home',
              style: new TextStyle(
                  color:
                  _selectedIndex == 0 ? selectedColor : unselectedColor)),
        ),
        BottomNavigationBarItem(
          icon: new Icon(Icons.show_chart,
              color: _selectedIndex == 1 ? selectedColor : unselectedColor),
          title: new Text('Month',
              style: new TextStyle(
                  color:
                  _selectedIndex == 1 ? selectedColor : unselectedColor)),
        ),
        BottomNavigationBarItem(
            icon: Icon(Icons.history,
                color: _selectedIndex == 2 ? selectedColor : unselectedColor),
            title: Text('History',
                style: new TextStyle(
                    color: _selectedIndex == 2
                        ? selectedColor
                        : unselectedColor))),
        BottomNavigationBarItem(
            icon: Icon(Icons.settings,
                color: _selectedIndex == 3 ? selectedColor : unselectedColor),
            title: Text('Settings',
                style: new TextStyle(
                    color: _selectedIndex == 3
                        ? selectedColor
                        : unselectedColor)))
      ],
    );
  }

Methods that update the state of the displayed page.

  void _onItemTapped(int index) {
    _changeDisplayedScreen(index);

    setState(() {
      _selectedIndex = index;
    });

  }

  void _changeDisplayedScreen(int index) {
    switch (index) {
      case 0:
        setState(() {
          _displayedPage = new LatestReadingPage();
        });
        break;
      case 1:
        setState(() {
          _displayedPage = new HomeScreen();
          //Placeholder
        });
        break;
      case 2:
        setState(() {
          _displayedPage = new HomeScreen();
          //Placeholder
        });
        break;
      case 3:
        setState(() {
          _displayedPage = new HomeScreen();
          //Placeholder
        });
        break;
      default:
        setState(() {
          _displayedPage = new LatestReadingPage();
        });
        break;
    }
  }
}

What I want is to be able to use the Flutter Navigation infrastructure, but only update the body property of the Scaffold widget when changing pages. Instead of the whole screen.

A lot like the Youtube app or Google news app.

Upvotes: 11

Views: 11055

Answers (3)

Xavier Soh
Xavier Soh

Reputation: 165

wrap your body inside the IndexedStack widget. Like this:

body: IndexedStack(
      index: selectedIndex,
      children: _children, //define a  widget children list
    ),

Here's a link https://medium.com/flutter/getting-to-the-bottom-of-navigation-in-flutter-b3e440b9386

Upvotes: 0

CZX
CZX

Reputation: 2027

I have added an answer to the post that you linked: https://stackoverflow.com/a/59133502/6064621

The answer below is similar to the one above, but I've also added unknown routes here:


What you want can be achieved by using a custom Navigator.

The Flutter team did a video on this, and the article they followed is here: https://medium.com/flutter/getting-to-the-bottom-of-navigation-in-flutter-b3e440b9386

Basically, you will need to wrap the body of your Scaffold in a custom Navigator:

class _MainScreenState extends State<MainScreen> {
  final _navigatorKey = GlobalKey<NavigatorState>();

  // ...

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Navigator(
        key: _navigatorKey,
        initialRoute: '/',
        onGenerateRoute: (RouteSettings settings) {
          WidgetBuilder builder;
          // Manage your route names here
          switch (settings.name) {
            case '/':
              builder = (BuildContext context) => HomePage();
              break;
            case '/page1':
              builder = (BuildContext context) => Page1();
              break;
            case '/page2':
              builder = (BuildContext context) => Page2();
              break;
            default:
              throw Exception('Invalid route: ${settings.name}');
          }
          // You can also return a PageRouteBuilder and
          // define custom transitions between pages
          return MaterialPageRoute(
            builder: builder,
            settings: settings,
          );
        },
      ),
      bottomNavigationBar: _yourBottomNavigationBar,
    );
  }
}

Within your bottom navigation bar, to navigate to a new screen in the new custom Navigator, you just have to call this:

_navigatorKey.currentState.pushNamed('/yourRouteName');

If you don't used named routes, then here is what you should do for your custom Navigator, and for navigating to new screens:

// Replace the above onGenerateRoute function with this one
onGenerateRoute: (RouteSettings settings) {
  return MaterialPageRoute(
    builder: (BuildContext context) => YourHomePage(),
    settings: settings,
  );
},
_navigatorKey.currentState.push(MaterialPageRoute(
  builder: (BuildContext context) => YourNextPage(),
));

To let Navigator.pop take you to the previous view, you will need to wrap the custom Navigator with a WillPopScope:

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: WillPopScope(
      onWillPop: () async {
        if (_navigatorKey.currentState.canPop()) {
          _navigatorKey.currentState.pop();
          return false;
        }
        return true;
      },
      child: Navigator(
        // ...
      ),
    ),
    bottomNavigationBar: _yourBottomNavigationBar,
  );
}

And that should be it! No need to manually handle pop too much or manage a custom history list.

Upvotes: 13

alexandreohara
alexandreohara

Reputation: 264

Have you tried this?

    return Scaffold(
      backgroundColor: Colors.white,
      appBar: EmptyAppBar(),
      body: _displayedPage[_selectedIndex],
      bottomNavigationBar: _createBottomNavigationbar,
    );

Then you make a widget list of all the Screens you want:

List<Widget> _displayedPage = [LatestReadingPage(), HomeScreen(), ExampleScreen()];

Upvotes: -1

Related Questions