How to programmatically select BottomNavigationBar Tab in Flutter instead of built in onTap callback?
Asked Answered
C

7

20

I have been working with BottomNavigationBar in the flutter, but I am not able to select a Tab programmatically outside of onTap callback of BottomNavigationBar.

The code with onTap callback, which is working:

    return new BottomNavigationBar(
  items: <BottomNavigationBarItem>[
    _bottomNavigationItem(Icons.account_circle, DrawerTitles.CONTACTS),
    _bottomNavigationItem(Icons.delete, DrawerTitles.DELETED_CONTACTS),
    _bottomNavigationItem(Icons.list, DrawerTitles.LOGS),
  ],
  onTap: (int index) {
    setState(() {
      navigationIndex = index;
      switch (navigationIndex) {
        case 0:
          handleBottomNavigationBarClicks(DrawerTitles.CONTACTS);
          break;
        case 1:
          handleBottomNavigationBarClicks(DrawerTitles.DELETED_CONTACTS);
          break;
        case 2:
          handleBottomNavigationBarClicks(DrawerTitles.LOGS);
          break;
      }
    });
  },
  currentIndex: navigationIndex,
  fixedColor: Colors.blue[400],
  type: BottomNavigationBarType.fixed,
);

But I want to change the tabs outside of onTap callback.

I have tried changing the index used by BottomNavigatioBar outside of onTap callBack, but it didn't work.

Here is what I have tried:

void changeTabs(int tabIndex) {
setState(() {
     navigationIndex = tabIndex;
});}

Here is a gist for the code.

Is there any way available to change Tabs?

Coniah answered 21/6, 2018 at 5:46 Comment(6)
Can you please provide your entire class code?Autotrophic
@JavidNoutash wait I am postingConiah
@JavidNoutash gist for the code addedConiah
Your code is a bit messy, there is a bit of duplication of logic, that's why you are a bit lost. Whenever you want to update your navigation bar tab: setState((){ apiHomeWidget = YOUR_TAB_PAGE; navigationIndex = YOUR_TAB_INDEX; });Autotrophic
i have tried that thing already which you mentioned already in the method handleBottomNavigationBarClicks but its not workingConiah
@JavidNoutash can u share some code if its working for youConiah
A
16

Here is a complete example on how to achieve what you want.

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

const String page1 = "Page 1";
const String page2 = "Page 2";
const String page3 = "Page 3";
const String title = "BNB Demo";

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: title,
      home: new MyHomePage(title: title),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<Widget> _pages;
  Widget _page1;
  Widget _page2;
  Widget _page3;

  int _currentIndex;
  Widget _currentPage;

  @override
  void initState() {
    super.initState();

    _page1 = Page1();
    _page2 = Page2();
    _page3 = Page3();

    _pages = [_page1, _page2, _page3];

    _currentIndex = 0;
    _currentPage = _page1;
  }

  void changeTab(int index) {
    setState(() {
      _currentIndex = index;
      _currentPage = _pages[index];
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: _currentPage,
      bottomNavigationBar: BottomNavigationBar(
          onTap: (index) => changeTab(index),
          currentIndex: _currentIndex,
          items: [
            BottomNavigationBarItem(
                title: Text(page1), icon: Icon(Icons.account_circle)),
            BottomNavigationBarItem(
                title: Text(page2), icon: Icon(Icons.account_circle)),
            BottomNavigationBarItem(
                title: Text(page3), icon: Icon(Icons.account_circle))
          ]),
      drawer: new Drawer(
        child: new Container(
          margin: EdgeInsets.only(top: 20.0),
          child: new Column(
            children: <Widget>[
              navigationItemListTitle(page1, 0),
              navigationItemListTitle(page2, 1),
              navigationItemListTitle(page3, 2),
            ],
          ),
        ),
      ),
    );
  }

  Widget navigationItemListTitle(String title, int index) {
    return new ListTile(
      title: new Text(
        title,
        style: new TextStyle(color: Colors.blue[400], fontSize: 22.0),
      ),
      onTap: () {
        Navigator.pop(context);
        changeTab(index);
      },
    );
  }
}

class Page1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text(page1),
    );
  }
}

class Page2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text(page2),
    );
  }
}

class Page3 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text(page3),
    );
  }
}

whenever you want to change to a tab, call the changeTab(YOUR_TAB_INDEX)

Autotrophic answered 21/6, 2018 at 7:19 Comment(5)
Is there any way to change tab from the child? If I have a button on Page 1 and on click of that I want to navigate the to 2nd tab(Page 2)Saarinen
@Saarinen you can use callback method to change bottom navigation bar dynamically from the child.Simsar
I tried to update this for the nagging null safety for us beginners.. but stackoverflow would not accept it even though i'm approved for edits.Morula
This solution isn't correct, it only calls a method from onTap callback, and the question is about change page programmatically instead that using the callBack, also this code is overly complicated and messy. The correct answer is the one from @shojaeddin which uses a GlobalKey, and if you declare it as static you can switch screens from other screens too.Tribune
how call that method(changeTab) and set state from another page and class?Feather
M
52

You can grab this BottomNavigationBar widget by using a GlobalKey. By this GlobalKey you can handle this widget. Here is an gist for the code

Here you assign a GlobalKey

GlobalKey globalKey = new GlobalKey(debugLabel: 'btm_app_bar');

And put that key in your BottomNavigationBar

new BottomNavigationBar(
    key: globalKey,
    items: [...],
   onTap: (int index) {...},
  ),

Now you can call widget's method and use CurvedNavigationBar instead of BottomNavigationBar if you have to work with CurvedNavigationBar.

 final BottomNavigationBar navigationBar = globalKey.currentWidget;
 navigationBar.onTap(2);
Monniemono answered 21/6, 2018 at 7:6 Comment(5)
Thanks for your answer will try and let you know if its working and mark your answer as accepted oneConiah
Searched ALL OVER trying to find this. Was just about to post the same question when I finally found this. Based on this Flutter Introductory video, this seems like the best way to go about things: youtube.com/watch?v=kn0EOS-ZiIcCloche
Isn't it a debugging feature?Orthopedist
Flutter 3.0 uses type casting and null aware operator. Remove all new keyword. You can also make it one liner. You also don't need debugLabel: 'btm_app_bar' in GlobalKey(). (globalKey.currentWidget as BottomNavigationBar).onTap!(1)Largo
You must use a Dynamic instead of BotoomNavigationBar : final dynamic navigationBar = globalKey.currentWidget; navigationBar.onTap(2);Geri
A
16

Here is a complete example on how to achieve what you want.

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

const String page1 = "Page 1";
const String page2 = "Page 2";
const String page3 = "Page 3";
const String title = "BNB Demo";

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: title,
      home: new MyHomePage(title: title),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<Widget> _pages;
  Widget _page1;
  Widget _page2;
  Widget _page3;

  int _currentIndex;
  Widget _currentPage;

  @override
  void initState() {
    super.initState();

    _page1 = Page1();
    _page2 = Page2();
    _page3 = Page3();

    _pages = [_page1, _page2, _page3];

    _currentIndex = 0;
    _currentPage = _page1;
  }

  void changeTab(int index) {
    setState(() {
      _currentIndex = index;
      _currentPage = _pages[index];
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: _currentPage,
      bottomNavigationBar: BottomNavigationBar(
          onTap: (index) => changeTab(index),
          currentIndex: _currentIndex,
          items: [
            BottomNavigationBarItem(
                title: Text(page1), icon: Icon(Icons.account_circle)),
            BottomNavigationBarItem(
                title: Text(page2), icon: Icon(Icons.account_circle)),
            BottomNavigationBarItem(
                title: Text(page3), icon: Icon(Icons.account_circle))
          ]),
      drawer: new Drawer(
        child: new Container(
          margin: EdgeInsets.only(top: 20.0),
          child: new Column(
            children: <Widget>[
              navigationItemListTitle(page1, 0),
              navigationItemListTitle(page2, 1),
              navigationItemListTitle(page3, 2),
            ],
          ),
        ),
      ),
    );
  }

  Widget navigationItemListTitle(String title, int index) {
    return new ListTile(
      title: new Text(
        title,
        style: new TextStyle(color: Colors.blue[400], fontSize: 22.0),
      ),
      onTap: () {
        Navigator.pop(context);
        changeTab(index);
      },
    );
  }
}

class Page1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text(page1),
    );
  }
}

class Page2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text(page2),
    );
  }
}

class Page3 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text(page3),
    );
  }
}

whenever you want to change to a tab, call the changeTab(YOUR_TAB_INDEX)

Autotrophic answered 21/6, 2018 at 7:19 Comment(5)
Is there any way to change tab from the child? If I have a button on Page 1 and on click of that I want to navigate the to 2nd tab(Page 2)Saarinen
@Saarinen you can use callback method to change bottom navigation bar dynamically from the child.Simsar
I tried to update this for the nagging null safety for us beginners.. but stackoverflow would not accept it even though i'm approved for edits.Morula
This solution isn't correct, it only calls a method from onTap callback, and the question is about change page programmatically instead that using the callBack, also this code is overly complicated and messy. The correct answer is the one from @shojaeddin which uses a GlobalKey, and if you declare it as static you can switch screens from other screens too.Tribune
how call that method(changeTab) and set state from another page and class?Feather
P
6

Thank you from @HuyHoàng for flutter version 2+ I use this:

var bottomWidgetKey=new GlobalKey<State<BottomNavigationBar>>();

then assign this key to BottomNavigationBar then can access bottom like below:

BottomNavigationBar navigationBar =  bottomWidgetKey.currentWidget as BottomNavigationBar;
navigationBar.onTap!(1);
Putnem answered 3/8, 2021 at 8:3 Comment(0)
B
4

Here is the complete example in answer to the first question above. The code is modified from the above. Note the passing of the callback method to Page 3 constructor.

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

const String page1 = "Home";
const String page2 = "Service";
const String page3 = "Profile";
const String title = "BNB Demo";

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: title,
      home: MyHomePage(title: title),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late List<Widget> _pages;
  late Widget _page1;
  late Widget _page2;
  late Widget _page3;
  late int _currentIndex;
  late Widget _currentPage;

  @override
  void initState() {
    super.initState();
    _page1 = const Page1();
    _page2 = const Page2();
    _page3 = Page3(changePage: _changeTab);
    _pages = [_page1, _page2, _page3];
    _currentIndex = 0;
    _currentPage = _page1;
  }

  void _changeTab(int index) {
    setState(() {
      _currentIndex = index;
      _currentPage = _pages[index];
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: _currentPage,
      bottomNavigationBar: BottomNavigationBar(
          onTap: (index) {
            _changeTab(index);
          },
          currentIndex: _currentIndex,
          items: const [
            BottomNavigationBarItem(
              label: page1,
              icon: Icon(Icons.home),
            ),
            BottomNavigationBarItem(
              label: page2,
              icon: Icon(Icons.home_repair_service),
            ),
            BottomNavigationBarItem(
              label: page3,
              icon: Icon(Icons.person),
            ),
          ]),
      drawer: Drawer(
        child: Container(
          margin: const EdgeInsets.only(top: 20.0),
          child: Column(
            children: <Widget>[
              _navigationItemListTitle(page1, 0),
              _navigationItemListTitle(page2, 1),
              _navigationItemListTitle(page3, 2),
            ],
          ),
        ),
      ),
    );
  }

  Widget _navigationItemListTitle(String title, int index) {
    return ListTile(
      title: Text(
        '$title Page',
        style: TextStyle(color: Colors.blue[400], fontSize: 22.0),
      ),
      onTap: () {
        Navigator.pop(context);
        _changeTab(index);
      },
    );
  }
}

class Page1 extends StatelessWidget {
  const Page1({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('$page1 Page', style: Theme.of(context).textTheme.headline6),
    );
  }
}

class Page2 extends StatelessWidget {
  const Page2({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('$page2 Page', style: Theme.of(context).textTheme.headline6),
    );
  }
}

class Page3 extends StatelessWidget {
  const Page3({Key? key, required this.changePage}) : super(key: key);
  final void Function(int) changePage;

  @override
  Widget build(BuildContext context) {
    return Align(
      alignment: Alignment.center,
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Text('$page3 Page', style: Theme.of(context).textTheme.headline6),
          ElevatedButton(
            onPressed: () => changePage(0),
            child: const Text('Switch to Home Page'),
          )
        ],
      ),
    );
  }
}
Bellda answered 19/9, 2021 at 9:39 Comment(0)
K
0

If you want to change the tab from inside a widget, you could do something like this:

Assume that this is the widget from where you want to change the currently active tab:

class MyWidget extends StatefulWidget {

  final Function() openHomeTab;

  MyWidget({
    this.openHomeTab,
  });
  @override
  _MyWidget createState() => _MyWidget();
}

class _MyWidget extends State<MyWidget> {
  @override
  Widget build(BuildContext context) {
    return CupertinoButton(
      onPressed: () {
        widget.openHomeTab();
      },
    );
  }
}

Now in the widget that contains the tab bar (BottomNavigationBar):

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: IndexedStack(
        index: _currentTab,
        children: _buildScreens(),
      ),
      bottomNavigationBar: BottomNavigationBar(
        showSelectedLabels: true,
        showUnselectedLabels: true,
        selectedItemColor: Colors.primary,
        //selectedLabelStyle: TextStyle(),
        elevation: 0,
        type: BottomNavigationBarType.fixed,
        onTap: (index) {
          openTab(index);
        },
        currentIndex: _currentTab,
        items: <BottomNavigationBarItem>[
          _buildTabIcon(0),
          _buildTabIcon(1),
          _buildTabIcon(2),
          _buildTabIcon(3),
          _buildTabIcon(4)
        ],
      ),
    );
  }

 
  List<StatefulWidget> _buildScreens() {
    _screens = <StatefulWidget>[
      ..., 
      ...,
      ...,
      MyWidget(
        openHomeTab: openHomeTab,
      )
    ];
    return _screens;
  }

  void openHomeTab() {
    openTab(0);
  }

  void openTab(int index) {
    setState(() {
      _currentTab = index;
    });
  }
Kidd answered 28/8, 2021 at 9:32 Comment(0)
C
0

In case anyone is having this same issue, mine was getting not just the next tab, but also allow the switching animation.

GlobalKey _bottomNavigationKey = GlobalKey();

The CurvedNavigationBar

 bottomNavigationBar: CurvedNavigationBar(
      key: _bottomNavigationKey,
      items: <Widget>[
        Icon(Icons.add, size: 25),
        Icon(Icons.abc, size: 25),
        Icon(Icons.home, size: 25),
      ],
      onTap: (index) {
        setState(() {
          _page = index;
        });
      },
    ),

Then somewhere in your code,

 RaisedButton(
   child: Text('Animate to Page 0'),
    onPressed: () {
     //Page change using state does the same as clicking index 0 navigation button

     final dynamic navBarState = _bottomNavigationKey.currentState;
     // Remember to use 'dynamic', instead of 'CurvedNavigationBarState'

     navBarState.setPage(0);
   },
 )
Cribbs answered 1/8, 2023 at 15:19 Comment(0)
S
-1

Another work around solution here for CupertinoApp. It is working for my app requirement.

It is also answer for "Is there any way to change tab from the child?", as we can pass controller in any child and we can change tab index.

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(CupertinoApp(home: HomeScreen(),));
}

final GlobalKey<NavigatorState> firstTabNavKey = GlobalKey<NavigatorState>();
final GlobalKey<NavigatorState> secondTabNavKey = GlobalKey<NavigatorState>();
final GlobalKey<NavigatorState> thirdTabNavKey = GlobalKey<NavigatorState>();

class HomeScreen extends StatelessWidget {

  var controller = CupertinoTabController(initialIndex: 0);

  @override
  Widget build(BuildContext context) {
    return CupertinoTabScaffold(
        controller: controller,
        tabBar: CupertinoTabBar(
          items: <BottomNavigationBarItem>[
            BottomNavigationBarItem(icon: Icon(CupertinoIcons.home)),
            BottomNavigationBarItem(icon: Icon(CupertinoIcons.cart)),
            BottomNavigationBarItem(icon: Icon(CupertinoIcons.person)),
          ],
        ),
        tabBuilder: (context, index) {
          if (index == 0) {
            return CupertinoTabView(navigatorKey: firstTabNavKey, builder: (context) => FirstTab(controller: this.controller,),);
          } else if (index == 1) {
            return CupertinoTabView(navigatorKey: secondTabNavKey, builder: (context) => SecondTab(),);
          } else {
            return CupertinoTabView(navigatorKey: thirdTabNavKey, builder: (context) => ThirdTab(),);
          }
        }
    );
  }
}

class FirstTab extends StatelessWidget {
  final CupertinoTabController controller;

  FirstTab({required this.controller});

  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold( child: Container(
      child: Center(
        child: Row(
          children: <Widget>[
            TextButton(onPressed: () => {controller.index = 1}, child: Text('Second Tab')),
            TextButton(onPressed: () => {controller.index = 2}, child: Text('Third Tab')),
          ],
        ),
      ),
    ));
  }
}

class SecondTab extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold( child:Container(child: Center( child: Text('Second Screen'),),));
  }
}

class ThirdTab extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold( child:Container(child: Center( child: Text('Third Screen'),),));
  }
}
Skinned answered 12/6, 2021 at 8:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.