Closing Drawer on Bottom Navigation Bar click, Flutter
A good way to do this is to use a GlobalKey for your scaffold. So, for all your scaffolds, you define them using:
class SomeClass extends StatelessWidget {
final scaffoldKey = GlobalKey<ScaffoldState>()
Widget build(BuildContext context) {
Scaffold(
backgroundColor: Colors.white,
drawer: Drawer(), // so this is what I want to close on BNB button press in each of the 4 screens
appBar: AppBar( // each screen has its own app bar
title: Text('Screens 1-4),
),
body: Text('Body of Screens 1-4),
key: scaffoldKey,
),
);
}
}
And then, you can pass this key to your BottomNavigationBar. In your BottomNavigationBar, you can have all the scaffoldKeys, and in the onItemTap function:
void _onItemTapped(int index) {
for (scaffoldKey in scaffoldKeys) {
// If the drawer is open
if (scaffoldKey.currentState.isDrawerOpen) {
// Closes the drawer
scaffoldKey.currentState?.openEndDrawer();
}
}
if (index == _selectedIndex) {
_items[index]
.navigationKey
.currentState
.popUntil((route) => route.isFirst);
} else {
setState(() {
_selectedIndex = index;
});
}
/// when the index is selected, on the button press do some actions
switch (_selectedIndex) {
case 0:
// Do some actions
break;
case 1:
// Do some actions
break;
case 2:
// Do some actions
break;
case 3:
// Do some actions
break;
}
}
It's up to you to find the best way of passing around the keys. You could for example define them in a Widgets that contains both the bottom navigation bar and the different scaffolds, and pass it down as parameters. You could use State Management... whatever fits your use case.
Here is what your code could look like:
class BottomNavBar extends StatefulWidget {
static const String id = 'bottom_navbar_screen';
@override
_BottomNavBarState createState() => _BottomNavBarState();
}
class _BottomNavBarState extends State<BottomNavBar> {
int _selectedIndex = 0;
late final List<GlobalKey<ScaffoldState>> scaffoldKeys;
/// list of screen that will render inside the BNB
late final List<Navigation> _items;
@override
initState() {
super.initState()
scaffoldKeys = [GlobalKey<ScaffoldState>(), GlobalKey<ScaffoldState>(), GlobalKey<ScaffoldState>(), GlobalKey<ScaffoldState>()];
_items = [
Navigation(
widget: Screen1(scaffoldKey: scaffoldKeys[0]), navigationKey: GlobalKey<NavigatorState>()),
Navigation(
widget: Screen2(scaffoldKey: scaffoldKeys[1]), navigationKey: GlobalKey<NavigatorState>()),
Navigation(
widget: Screen3(scaffoldKey: scaffoldKeys[2]), navigationKey: GlobalKey<NavigatorState>()),
Navigation(
widget: Screen4(scaffoldKey: scaffoldKeys[3]), navigationKey: GlobalKey<NavigatorState>()),
];
}
/// function that renders components based on selected one in the BNB
void _onItemTapped(int index) {
for (scaffoldKey in scaffoldKeys) {
// If the drawer is open
if (scaffoldKey.currentState.isDrawerOpen) {
// Closes the drawer
scaffoldKey.currentState?.openEndDrawer();
}
}
if (index == _selectedIndex) {
_items[index]
.navigationKey
.currentState
.popUntil((route) => route.isFirst);
} else {
setState(() {
_selectedIndex = index;
});
}
/// when the index is selected, on the button press do some actions
switch (_selectedIndex) {
case 0:
// Do some actions
break;
case 1:
// Do some actions
break;
case 2:
// Do some actions
break;
case 3:
// Do some actions
break;
}
}
/// navigation Tab widget for a list of all the screens and puts them in a Indexed Stack
Widget _navigationTab(
{GlobalKey<NavigatorState> navigationKey, Widget widget, GlobalKey<ScaffoldState> scaffoldKey}) {
return Navigator(
key: navigationKey,
onGenerateRoute: (routeSettings) {
return MaterialPageRoute(builder: (context) => widget);
},
);
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
final isFirstRouteInCurrentTab =
!await _items[_selectedIndex].navigationKey.currentState.maybePop();
if (isFirstRouteInCurrentTab) {
if (_selectedIndex != 0) {
_onItemTapped(1);
return false;
}
}
/// let system handle back button if we're on the first route
return isFirstRouteInCurrentTab;
},
child: Scaffold(
body: IndexedStack(
index: _selectedIndex,
children: _items
.map((e) => _navigationTab(
navigationKey: e.navigationKey, widget: e.widget))
.toList(),
),
bottomNavigationBar: BottomNavigationBar(
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
label: 'Screen 1,
),
BottomNavigationBarItem(
label: 'Screen 2,
),
BottomNavigationBarItem(
label: 'Screen 3,
),
BottomNavigationBarItem(
label: 'Screen 4,
),
],
currentIndex: _selectedIndex,
showUnselectedLabels: true,
onTap: _onItemTapped,
),
),
);
}
}
And you screens:
class Screen1 extends StatelessWidget {
final GlobalKey<ScaffoldState> scaffoldKey;
Screen1({required this.scaffoldKey});
@override
Widget build(BuildContext context) {
return Scaffold(
key: scaffoldKey,
backgroundColor: Colors.white,
drawer: Drawer(), // so this is what I want to close on BNB button press in each of the 4 screens
appBar: AppBar( // each screen has its own app bar
title: Text('Screens 1-4'),
),
body: Text('Body of Screens 1-4'),
);
}
}
I changed the list of screens _items to a late variables so you can pass the scaffoldKeys to them when declaring them.