Home > OS >  Flutter BottomNavigationBar changes only screens but doesn't selection
Flutter BottomNavigationBar changes only screens but doesn't selection

Time:01-11

Hi I'm new to Flutter and I'm trying to add BottomNavigationBar to my application, but I got a problem as I wrote in title.

As you can see below, I'm placing a state of currentIndex and a method called when tapped in a different class.

page_frame.dart (This has a state and a method)

// ...

class PageFrame extends StatefulWidget {
  const PageFrame({super.key});

  @override
  State<PageFrame> createState() => _PageFrameState();
}

class _PageFrameState extends State<PageFrame> {
  int _currentPageIndex = 0;

  final List<Widget> _pages = <Widget>[
    const Home(),
    const Announcement()
  ];

  void _onPageTap(int index) {
    setState(() {
      _currentPageIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor     : const Color(0xFF183977),
      body                : _pages.elementAt(_currentPageIndex),
      bottomNavigationBar : SPBottomNavigationBar(
        currentIndex : _currentPageIndex,
        onTap        : _onPageTap
      ),
    );
  }
}

my_bottom_navigation_var.dart (This receives a state and a method from above)

// ...

class MyBottomNavigationBar extends StatefulWidget {
  final int currentIndex;
  final ValueChanged<int> onTap;
  const MyBottomNavigationBar({required this.currentIndex, required this.onTap, Key? key})
    : super(key: key);

  @override
  State<MyBottomNavigationBar> createState() => _MyBottomNavigationBarState();
}

class _MyBottomNavigationBarState extends State<MyBottomNavigationBar> {
  late int currentIndex;
  late ValueChanged<int> onTap;

  @override
  void initState() {
    super.initState();
    currentIndex = widget.currentIndex;
    onTap        = widget.onTap;
  }

  @override
  Widget build(BuildContext context) {
    return BottomNavigationBar(
      unselectedItemColor : const Color(0xFFFFFFFF),
      selectedItemColor   : const Color(0xFF008BF1),
      showUnselectedLabels: true,
      items               : const [
        BottomNavigationBarItem(
          icon            : Icon(Icons.home),
          label           : 'Home',
          backgroundColor : Color(0xFF000000)
        ),
        BottomNavigationBarItem(
          icon            : Icon(Icons.announcement),
          label           : 'Announcement',
          backgroundColor : Color(0xFF000000)
        )
      ],
      currentIndex : currentIndex,
      onTap        : onTap,
    );
  }
}

This results in if I tap an icon of Announcement, only the screen changes from Home to Announcement, but what icon of selected is still Home like this: Screen : Home, Selection : Home Screen : Announcement, Selection : Home I thought this could happens because of lack of interactivity, even if it were, I have no idea how to get this interactive. Please tell me what to do. Thanks.

CodePudding user response:

Your MyBottomNavigationBar state class has its life-cycle, It gets currentIndex value for the 1st time on initState. You can directly access the widget class variable with

widget.variableName

You can use like

class MyBottomNavigationBar extends StatefulWidget {
  final int currentIndex;
  final ValueChanged<int> onTap;
  const MyBottomNavigationBar(
      {required this.currentIndex, required this.onTap, Key? key})
      : super(key: key);

  @override
  State<MyBottomNavigationBar> createState() => _MyBottomNavigationBarState();
}

class _MyBottomNavigationBarState extends State<MyBottomNavigationBar> {
  @override
  Widget build(BuildContext context) {
    return BottomNavigationBar(
      unselectedItemColor: const Color(0xFFFFFFFF),
      selectedItemColor: const Color(0xFF008BF1),
      showUnselectedLabels: true,
      items: const [
        BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: 'Home',
            backgroundColor: Color(0xFF000000)),
        BottomNavigationBarItem(
            icon: Icon(Icons.announcement),
            label: 'Announcement',
            backgroundColor: Color(0xFF000000))
      ],
      currentIndex: widget.currentIndex,
      onTap: widget.onTap,
    );
  }
}

Also you can just use stateless widget here, the parent(home) widget pass new value on index changes and this widget get rebuild automatically.

class MyBottomNavigationBar extends StatelessWidget {
  final int currentIndex;
  final ValueChanged<int> onTap;
  const MyBottomNavigationBar(
      {required this.currentIndex, required this.onTap, Key? key})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BottomNavigationBar(
      unselectedItemColor: const Color(0xFFFFFFFF),
      selectedItemColor: const Color(0xFF008BF1),
      showUnselectedLabels: true,
      items: const [
        BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: 'Home',
            backgroundColor: Color(0xFF000000)),
        BottomNavigationBarItem(
            icon: Icon(Icons.announcement),
            label: 'Announcement',
            backgroundColor: Color(0xFF000000))
      ],
      currentIndex: currentIndex,
      onTap: onTap,
    );
  }
}

CodePudding user response:

The reason your main screen is updating while the NavigationBar is not is simply because they have two different states.

In your main screen, you have a variable with the current page index and when the user taps the NavigationBar you update this and redraw the widget. This will not redraw the child widget, which has its own state that hasn't changed.

There are two simple fixes for this.

NavigationBar as StatelessWidget

Since the Navbar never uses its state, you could convert it into a StatelessWidget and pass the active index in the constructor. That way, when the main screen changes it will rebuild the NavigationBar with the new index.

class MyBottomNavigationBar extends StatelessWidget {
  final int currentIndex;
  final ValueChanged<int> onTap;
  const MyBottomNavigationBar(
      {Key? key, required this.currentIndex, required this.onTap})
      : super(key: key);
  // ...

NavigationBar as StatefulWidget

The other way to fix this is to keep the NavigationBar as a StatefulWidget but when the user taps you update the state in here and also pass up the selected index. Just change the onTap() as follows:

Instead of onTap: onTap you will write

onTap: (index) {
  setState((){
    currentIndex = index;
  });
  widget.onTap(index);
}

As you can see, you don't even need to create a state variable for the callback function inside the initState(). Just call it here.

  • Related