How to detect TabBar change in Flutter?

32,528

Solution 1

You need to add a listener to your tab controller - maybe in initState.

controller.addListener((){
   print('my index is'+ controller.index.toString());
});

Solution 2

Swipe functionality is not provided by onTap() function, for that TabController is used

class TabBarDemo extends StatefulWidget {
  @override
  _TabBarDemoState createState() => _TabBarDemoState();
}

class _TabBarDemoState extends State<TabBarDemo>
    with SingleTickerProviderStateMixin {
  TabController _controller;
  int _selectedIndex = 0;

  List<Widget> list = [
    Tab(icon: Icon(Icons.card_travel)),
    Tab(icon: Icon(Icons.add_shopping_cart)),
  ];

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    // Create TabController for getting the index of current tab
    _controller = TabController(length: list.length, vsync: this);

    _controller.addListener(() {
      setState(() {
        _selectedIndex = _controller.index;
      });
      print("Selected Index: " + _controller.index.toString());
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          bottom: TabBar(
            onTap: (index) {
              // Should not used it as it only called when tab options are clicked,
              // not when user swapped
            },
            controller: _controller,
            tabs: list,
          ),
          title: Text('Tabs Demo'),
        ),
        body: TabBarView(
          controller: _controller,
          children: [
            Center(
                child: Text(
                  _selectedIndex.toString(),
                  style: TextStyle(fontSize: 40),
                )),
            Center(
                child: Text(
                  _selectedIndex.toString(),
                  style: TextStyle(fontSize: 40),
                )),
          ],
        ),
      ),
    );
  }
}

Github Repo:

https://github.com/jitsm555/Flutter-Problems/tree/master/tab_bar_tricks

Output:

enter image description here

Solution 3

Here is a full example. Use a TabController and add a callback using addListener.

import 'package:flutter/material.dart';

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

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

class MyTabbedPage extends StatefulWidget {
  const MyTabbedPage({Key key}) : super(key: key);
  @override
  _MyTabbedPageState createState() => _MyTabbedPageState();
}

class _MyTabbedPageState extends State<MyTabbedPage> with SingleTickerProviderStateMixin {
  var _context;

  final List<Tab> myTabs = <Tab>[
    Tab(text: 'LEFT'),
    Tab(text: 'RIGHT'),
  ];

  TabController _tabController;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(vsync: this, length: myTabs.length);

    _tabController.addListener(_handleTabSelection);
  }

  void _handleTabSelection() {
    if (_tabController.indexIsChanging) {
      switch (_tabController.index) {
        case 0:
          Scaffold.of(_context).showSnackBar(SnackBar(
            content: Text('Page 1 tapped.'),
            duration: Duration(milliseconds: 500),
          ));
          break;
        case 1:
          Scaffold.of(_context).showSnackBar(SnackBar(
            content: Text('Page 2 tapped.'),
            duration: Duration(milliseconds: 500),
          ));
          break;
      }
    }
  }

  @override
  void dispose() {
    _tabController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        bottom: TabBar(
          controller: _tabController,
          tabs: myTabs,
        ),
      ),
      body: Builder(
        builder: (context) {
          _context = context;
          return TabBarView(
            controller: _tabController,
            children: myTabs.map((Tab tab) {
              final String label = tab.text.toLowerCase();
              return Center(
                child: Text(
                  'This is the $label tab',
                  style: const TextStyle(fontSize: 36),
                ),
              );
            }).toList(),
          );
        },
      ),
    );
  }
}

Solution 4

enter image description here

Warp your tab in BottomNavigationBar . it will give you option onTap() where you can check which tab will clicked.

using this code you will also redirect to particular page when you tap on Tab

import 'package:flutter/material.dart';
    
    
    class BottomBarList extends StatefulWidget {
      @override
      _BottomBarListState createState() => _BottomBarListState();
    }
    
    class _BottomBarListState extends State<BottomBarList> {
      int bottomSelectedIndex = 0;
      int _selectedIndex = 0;
    
    
      List<Widget> _widgetOptions = <Widget>[
        AllMovieList(),
        MovieDescription(),
    
      ];
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          //  appBar: AppBar(),
          bottomNavigationBar: bottomBar(),
          body:_widgetOptions.elementAt(_selectedIndex),
        );
      }
    
      bottomBar() {
        return BottomNavigationBar(
    
          type: BottomNavigationBarType.shifting,
          unselectedItemColor: Colors.grey,
          items: const <BottomNavigationBarItem>[
            BottomNavigationBarItem(
              icon: Icon(Icons.tv),
              title: Text('Home'),
            ),
            BottomNavigationBarItem(
              icon: Icon(Icons.star),
              title: Text('Business'),
            ),
          ],
          currentIndex: _selectedIndex,
          selectedItemColor: Colors.black,
          backgroundColor: Colors.orange,
          onTap: _onItemTapped,
        );
      }
    
    
      void _onItemTapped(int index) {
        setState(() {
          _selectedIndex = index;
        });
      }
    }

Solution 5

You can create wrapper widget

class _DefaultTabControllerListener extends StatefulWidget {
  const _DefaultTabControllerListener(
      {Key? key, this.onTabSelected, required this.child})
      : super(key: key);

  final void Function(int index)? onTabSelected;
  final Widget child;

  @override
  _DefaultTabControllerListenerState createState() =>
      _DefaultTabControllerListenerState();
}

class _DefaultTabControllerListenerState
    extends State<_DefaultTabControllerListener> {
  late final void Function()? _listener;
  TabController? _tabController;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance?.addPostFrameCallback((_) {
      final tabController = DefaultTabController.of(context)!;
      _listener = () {
        final onTabSelected = widget.onTabSelected;
        if (onTabSelected != null) {
          onTabSelected(tabController.index);
        }
      };
      tabController.addListener(_listener!);
    });
  }
  
  @override
  void didChangeDependencies() {
    _tabController = DefaultTabController.of(context);
    super.didChangeDependencies();
  }

  @override
  void dispose() {
    if (_listener != null && _tabController != null) {
      _tabController!.removeListener(_listener!);
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return widget.child;
  }

}

And wrap TabBar with this widget

DefaultTabController(
        child: _DefaultTabControllerListener(
                  onTabSelected: (index) {
                    // Handler
                  },
                  child: TabBar(.....
Share:
32,528
Ashtav
Author by

Ashtav

I am fullstack developer, I write code and also make music, I love to play guitar, drum, violin and piano.

Updated on June 30, 2021

Comments

  • Ashtav
    Ashtav almost 3 years

    I need to detect TabBar when I swipe then print somethings on console, how I can do that? This is my code.

    bottomNavigationBar: new Material(
                 color: Colors.blueAccent,
                 child: new TabBar(
                   onTap: (int index){ setState(() {
                     _onTap(index);
                   });},
    
                   indicatorColor: Colors.white,
                   controller: controller,
                   tabs: <Widget>[
                     new Tab(icon: new Icon(Icons.shopping_basket)),
                     new Tab(icon: new Icon(Icons.store)),
                     new Tab(icon: new Icon(Icons.local_offer)),
                     new Tab(icon: new Icon(Icons.assignment)),
                     new Tab(icon: new Icon(Icons.settings)),
    
                   ],
                 )
               ),
    
  • nonybrighto
    nonybrighto about 5 years
    Where did you add it? Can you update your code so I can see how you used it?
  • Abdurahman Popal
    Abdurahman Popal about 4 years
    We can't detect tab scroll by NeverScrollableScrollPhysics, we disable swiping by it.
  • Mohsen Emami
    Mohsen Emami about 4 years
    Disabling swiping TabBarView enables you to detect changing between tabs only by tapping on Tabs, that is because there is no listener for swiping tabs
  • Abdurahman Popal
    Abdurahman Popal about 4 years
    We can listen to tab scroll notification by using NotificationListener Widget stackoverflow.com/a/60353215/10020712
  • Vinoth Vino
    Vinoth Vino over 3 years
    indexIsChanging is true only if we tap the tab bar item otherwise it's false (when swipe the tab bar view)
  • Expressingx
    Expressingx over 3 years
    Works nice, but addListener() gets called twice on tab change. Any way to remove that?
  • Admin
    Admin about 3 years
    The listener never gets called for me even after adding the controller to the TabBar and the TabBarView
  • Raul Mabe
    Raul Mabe about 3 years
    @VinothVino Yeah, but if (!_tabController.indexIsChanging) print(_tabController.index);actually prints once every time you switch tabs (by swiping AND by tapping). This works as the listener gets called twice when switching tabs by tapping
  • Hide in bubble
    Hide in bubble almost 3 years
    @Expressingx Did you find solution?
  • Drunken Daddy
    Drunken Daddy about 2 years
    if (tabController.indexIsChanging == true) { return; } Need this, or else the listener will be called twice while touching a tab to change the tab. It will work without this when tab is changed by swiping
  • Maqcel
    Maqcel almost 2 years
    @Expressingx & @tdtkien after tab change you dispose of the state. Afterward, call initState once more so that each time you change the tab new listener is being created. You can add a listener to some variable and add it along with removing the listener to the dispose method. That way you ensure just one listener is up. (Directly the one in the tab you are currently at)