8

I'm trying to navigate to different pages within my app using the icons in my bottom navigation bar. I have tried many tutorials and can't seem to work out the best way to achieve this. I have created my Homepage (code below) and 2 additional pages, Inbox and Signin, both return simple scaffolds.

Firstly i'm interested to know if this is the best way to do what i'm trying to achieve and second, how can my code be altered to allow me to navigate to different pages depending on which icon is tapped. I'm aware that the code below doesn't execute, i'm just trying to show what i've tried.

My code:

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {

  _onTap(int index) {
    Navigator.of(context)
        .push(MaterialPageRoute<Null>(builder: (BuildContext context) {
      return _children[_currentIndex];
    }));}

  final List<Widget> _children = [
    HomePage(),
    InboxPage(),
    SignInPage()
  ];

  int _currentIndex = 0;

  @override
  Widget build(BuildContext context) {
      SizeConfig().init(context);
      return Scaffold(
        appBar: PreferredSize(preferredSize: Size(double.infinity, 75),
          child: AppBar(
              elevation: 0.0,
              centerTitle: false,
              title: Column(
                children: <Widget>[
                  Align(
                    alignment: Alignment.centerLeft,
                    child: Text(
                      currentDate,
                      textAlign: TextAlign.left,
                      style: TextStyle(
                          color: titleTextColor,
                          fontWeight: subTitleFontWeight,
                          fontFamily: titleFontFamily,
                          fontSize: subTitleFontSize),
                    ),
                  ),
                  SizedBox(
                    height: 15,
                  ),
                  Align(
                    alignment: Alignment.centerLeft,
                    child: Text(
                      'Some text here',
                      style: TextStyle(
                          color: titleTextColor,
                          fontWeight: titleTextFontWeight,
                          fontFamily: titleFontFamily,
                          fontSize: titleFontSize),
                    ),
                  ),
                ],
              ),
              backgroundColor: kPrimaryColor,
              shape: titleBarRounding
          ),
        ),
        body: BodyOne(),
        bottomNavigationBar: BottomNavigationBar(
            currentIndex: _currentIndex,
            type: BottomNavigationBarType.fixed,
            items: [
              BottomNavigationBarItem(
                icon: Icon(Icons.home),
                title: Text('Home'),
              ),
              BottomNavigationBarItem(
                icon: Icon(Icons.mail),
                title: Text('Inbox'),
              ),
              BottomNavigationBarItem(
                icon: Icon(Icons.account_circle),
                title: Text('Account'),


              )
            ],
          onTap: () => _onTap(_currentIndex),
        ),);
    }
  }

Thanks in advance.

TJMitch95
  • 421
  • 3
  • 7
  • 23
  • Is this really what you want to do? What you usually do with a BottomNavigationBar is replacing the body of the Scaffold which also contains the BottomNavigationBar without using the Navigator. So in the onTap you are setting the _currentIndex like also mentioned in the answer of Mobina and then the body only contains _children[_currentIndex] – NiklasLehnfeld Aug 06 '20 at 19:58
  • Maybe this isn't what i'm looking for. Apologies if my question is lacking clarity. I basically have 3 pages, each need the same bottom nav bar (current page highlighted) but with the app bar and body changing depending on which page I am on. Do you think I should adopt a different approach? Thanks – TJMitch95 Aug 07 '20 at 09:39
  • Yes. I think so! The answer which DidierPeran Ganthier gave is very good. This is the usual way to go. – NiklasLehnfeld Aug 07 '20 at 22:08

3 Answers3

18

The screen you are in can't be part of the Screens you're navigating to and you don't need to push a new screen each time you just have to change selectedPage, this is an example of how it should look:

import 'package:flutter/material.dart';

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  int selectedPage = 0;

  final _pageOptions = [
    HomeScreen(),
    InboxScreen(),
    SignInScreen()
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        backgroundColor: Colors.white,
        body: _pageOptions[selectedPage],
        bottomNavigationBar: BottomNavigationBar(
          items: [
            BottomNavigationBarItem(icon: Icon(Icons.home, size: 30), title: Text('Home')),
            BottomNavigationBarItem(icon: Icon(Icons.mail, size: 30), title: Text('Inbox')),
            BottomNavigationBarItem(icon: Icon(Icons.account_circle, size: 30), title: Text('Account')),
          ],
          selectedItemColor: Colors.green,
          elevation: 5.0,
          unselectedItemColor: Colors.green[900],
          currentIndex: selectedPage,
          backgroundColor: Colors.white,
          onTap: (index){
            setState(() {
              selectedPage = index;
            });
          },
        )
    );
  }
}

Let me know if you need more explanation.

1
  1. The input parameter of the _onTap function is unused and needs to be deleted.
_onTap() {
    Navigator.of(context)
        .push(MaterialPageRoute(builder: (BuildContext context) => _children[_currentIndex])); // this has changed
  }
  1. In the onTap of the BottomNavigationBar you need to change the _currentIndex and then call the _onTap function which navigates to the selected screen.
onTap: (index) {
          setState(() {
            _currentIndex = index;
          });
          _onTap();
        },

You can add this BottomNavigationBar to all of the screens, but pay attention to the initial value of the _currentIndex that changes according to the screen you're putting the BottomNavigationBar in.

Full code:

_onTap() { // this has changed
    Navigator.of(context)
        .push(MaterialPageRoute(builder: (BuildContext context) => _children[_currentIndex])); // this has changed
  }

  final List<Widget> _children = [
    HomePage(),
    InboxPage(),
    SignInPage()
  ];

  @override
  Widget build(BuildContext context) {
    SizeConfig().init(context);
    return Scaffold(
      appBar: PreferredSize(
        preferredSize: Size(double.infinity, 75),
        child: AppBar(
          elevation: 0.0,
          centerTitle: false,
          title: Column(
            children: <Widget>[
              Align(
                alignment: Alignment.centerLeft,
                child: Text(
                  currentDate,
                  textAlign: TextAlign.left,
                    style: TextStyle(
                        color: titleTextColor,
                        fontWeight: subTitleFontWeight,
                        fontFamily: titleFontFamily,
                        fontSize: subTitleFontSize),
                ),
              ),
              SizedBox(
                height: 15,
              ),
              Align(
                alignment: Alignment.centerLeft,
                child: Text(
                  'Some text here',
                    style: TextStyle(
                        color: titleTextColor,
                        fontWeight: titleTextFontWeight,
                        fontFamily: titleFontFamily,
                        fontSize: titleFontSize),
                ),
              ),
            ],
          ),
            backgroundColor: kPrimaryColor,
            shape: titleBarRounding
        ),
      ),
      body: BodyOne(),
      body: Container(),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        type: BottomNavigationBarType.fixed,
        items: [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            title: Text('Home'),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.mail),
            title: Text('Inbox'),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.account_circle),
            title: Text('Account'),
          )
        ],
        onTap: (index) { // this has changed
          setState(() {
            _currentIndex = index;
          });
          _onTap();
        },
      ),
    );
  }
Mobina
  • 6,369
  • 2
  • 25
  • 41
0

The best way to do it is creating a wrapper to your screens. Like this:

class Wrapper extends StatefulWidget {
  Wrapper();

  _WrapperState createState() => _WrapperState();
}

class _WrapperState extends State<Wrapper> {
  int _currentIndex = 0;
  final List<Widget> _children = [
    HomePage(),
    InboxPage(),
    SignInPage()
  ];

  void onTabTapped(int index) {
    setState(() {
      _currentIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _children[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        onTap: onTabTapped,
        currentIndex: _currentIndex,
        items:[
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            title: Text('Home'),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.mail),
            title: Text('Inbox'),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.account_circle),
            title: Text('Account'),
          )
        ],
      ),
    );
  }
}