On one of the pages, nested inside one of bottom navigation bar pages I want to hide the bottom navigation bar, which is set as global. To be clear, I'm talking about this bar:
I can't just use Navigator.pushNamed because I'm creating viewModel and passing arguments in this way:
openConversation(BuildContext context) async {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => ChangeNotifierProvider(
create: (context) => ConversationVM(...),
child: ConversationScreen(
(...)
),
),
));
}
I've tried to set the Scaffold parameter bottomNavigationBar to null, but without effect, I need to resolve that problem somewhere higher.
Nav bar snippet:
class NavigationBar extends StatefulWidget {
static String id = 'navigation_screen';
#override
State<StatefulWidget> createState() {
return _NavigationBarState();
}
}
class _NavigationBarState extends State<NavigationBar> {
//track the index of our currently selected tab
int _currentIndex = 0;
//st of widgets that we want to render
final List<Object> _children = [
PostedQueries(),
];
#override
Widget build(BuildContext context) {
return Scaffold(
//body of our scaffold which is the widget that gets displayed between our app bar and bottom navigation bar.
body: _children[_currentIndex], // new
bottomNavigationBar: BottomNavigationBar(
onTap: onTabTapped,
// new
currentIndex: _currentIndex,
// new
unselectedItemColor: Colors.grey,
selectedItemColor: Colors.deepOrange,
items: [
BottomNavigationBarItem(
icon: new Icon(Icons.home),
label: ('Home'),
),
],
),
);
}
void onTabTapped(int index) {
setState(() {
_currentIndex = index;
});
}
}
Related
I have a bottom navigation bar with 4 tabs; Home, Loans, Insurance and Settings. The Settings page has buttons leading to many other pages but those pages are not supposed to be on the navigation bar. However the navigation bar needs to persist across all the pages to make it easy to go back to the main pages like the Home Page. Below is the code for my bottom navigation bar:
import 'package:bima/Insurance/insurance.dart';
import 'package:bima/Loans/loans.dart';
import 'package:bima/Settings/settings.dart';
import 'package:flutter/material.dart';
import '../Home/home.dart';
class Dashboard extends StatefulWidget {
const Dashboard({Key? key}) : super(key: key);
#override
_DashboardState createState() => _DashboardState();
}
class _DashboardState extends State<Dashboard> {
int _currentIndex = 0;
final tabs = [
const Home(),
const Loans(),
const Insurance(),
const Settings()
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: tabs[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
unselectedItemColor: Colors.black,
selectedItemColor: const Color.fromRGBO(212, 164, 24, 1),
currentIndex: _currentIndex,
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.wallet_outlined),
label: 'Loans',
),
BottomNavigationBarItem(
icon: Icon(Icons.shield_moon_outlined),
label: 'Insurance',
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: 'Settings',
),
],
),
);
}
}
This is a picture of the pages with a navigation bar and the pages without:
How can I make sure the navigation bar appears and works across all the pages without having to add extra tabs to the index? Edit: The code I have posted works so far for navigating the pages in the index namely: Home, Loans, Insurance and Settings. I do not want to add any other pages to the index, I would just like to display the navigation bar on the other pages which are not included in the index.
1 - Take a look at this package: https://pub.dev/packages/google_nav_bar.
2 - check that your bottom nav is implemented in the right place.
Instead of using BottomNavigationBar use TabBar it has controller that can be used.
First initialize _tabController in init
late TabController _tabController;
void initState() {
super.initState();
_tabController = TabController(length: 2, initialIndex: 0, vsync: this);
//_tabController.addListener(_handleTabChange);
}
void _handleTabChange() async {
//controller handel tab change by default so no need to handle it here
//this is anything custom you want to perform when tab is changed
}
final tabIcons = [
// Homeicon,
// Loansicon,
// Insuranceicon,
// Settingsicon
];
bottom: TabBar(
controller: _tabController,
indicatorColor: Colors.red,
tabs: tabIcons
),
And in body use with list of tabs as you declared above
TabBar(
controller: _tabController,
indicatorColor: Colors.red,
tabs: tabs, //list of tabs as you declared already use here a
),
And if you want to continue on with BottomNavigationBar follow below instructions
It might not be optimal solution but it was working for me and hope so it will also work for you, on navigating use Navigator.pushReplacement if you don't want to navigate back to last page visited, and if you want to navigate to previous page on back press use simple Navigator.push
onTap use this instead of only updating current index
onTap: (index) {
setState(() {
_currentIndex = index;
});
goToIndexPage(index);
},
goToIndexPage(int index) {
if (index == 0) {
//navigate to 1st
// Navigator.pushReplacement(
// context,
// MaterialPageRoute(builder: (context) => const Home()),
// );
} else if (index == 1) {
//navigate to 2nd page
// Navigator.pushReplacement(
// context,
// MaterialPageRoute(builder: (context) => const Loan()),
// );
} else if (index == 2) {
//navigate to 3rd page
} else if (index == 3) {
//navigate to 4th page
}
}
I want to close my Drawer widget every time the user presses a button in the Bottom Navigation Bar, but can't quite figure this thing out. The way my setup of the BNB is set now is that the current state of all screen is remembered through out the app (using an IndexedStack), but I want to close the Drawer if it is opened in any of the screens before the BNB button press. Each of my screens have their own Drawers and AppBars, so I can't make one Drawer inside the BNB (or I can and I can dynamically change them with a switch case when a specific Screen is clicked on BUT then the Drawer will cover the Bottom Navigation Bar etc.), but I want to make it work like this for now. So here is the code with some comments inside to explain things:
Bottom Navigation Bar:
class BottomNavBar extends StatefulWidget {
static const String id = 'bottom_navbar_screen';
#override
_BottomNavBarState createState() => _BottomNavBarState();
}
class _BottomNavBarState extends State<BottomNavBar> {
int _selectedIndex = 0;
/// list of screen that will render inside the BNB
List<Navigation> _items = [
Navigation(
widget: Screen1(), navigationKey: GlobalKey<NavigatorState>()),
Navigation(
widget: Screen2(), navigationKey: GlobalKey<NavigatorState>()),
Navigation(
widget: Screen3(), navigationKey: GlobalKey<NavigatorState>()),
Navigation(
widget: Screen4(), navigationKey: GlobalKey<NavigatorState>()),
];
/// function that renders components based on selected one in the BNB
void _onItemTapped(int index) {
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}) {
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,
),
),
);
}
}
Let's say all 4 screens are the same and they have their own AppBar and Drawer:
#override
Widget build(BuildContext context) {
return 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'),
);
}
So because each of the screens have their own AppBars and Drawers, the Drawer doesn't render over the Bottom Navigation Bar, so my BNB buttons can be clicked. If I put one Drawer for all Screens inside the BNB, then you can't click the BNB unless you close the Drawer first, which is not something I'm looking for right now.
So, my final question is, how do I close each of the Screens Drawers (if they are previously opened that is) when you press the BottomnavigationBar? (i.e. I am on Screen 1, I open the Drawer, then I press on Screen 2 in the BNB and I want to pop()/close the Drawer in Screen 1 before I navigate to Screen 2.)
Thanks in advance for your help!
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.
I am building an app with a bottom appbar with classic indexed pages to navigate the main menu:
class OverAllScreen extends StatefulWidget {
final int initialPage;
OverAllScreen(this.initialPage);
#override
_OverAllScreenState createState() => _OverAllScreenState();
}
class _OverAllScreenState extends State<OverAllScreen> {
List _pageOptions = [
Shop(),
Home(),
Discover(),
Account(),
];
int _page;
#override
void initState() {
super.initState();
setState(() {
_page = widget.initialPage;
});
}
#override
Widget build(BuildContext context) {
final _theme = Theme.of(context);
final _mediaSize = MediaQuery.of(context).size;
return Scaffold(
body: _pageOptions[_page],
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
backgroundColor: _theme.primaryColor,
selectedItemColor: _theme.accentColor,
unselectedItemColor: Colors.white,
currentIndex: _page,
onTap: (index) {
setState(() {
_page = index;
});
},
items: [
BottomNavigationBarItem(icon: Icon(Icons.shop), label: 'Shop'),
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Discover'),
BottomNavigationBarItem(
icon: Icon(Icons.person_outline), label: 'Account'),
],
),
);
}
}
In the account page, further down the widget tree I have a widget that shows a list of detailed products.
I want this page to open up when I click on an item of a simple (non-detailed) grid of products.
This I can easily do with Navigator.of(context).push(MaterialPageR0ute(...))). However I would like to keep the bottomAppBAr visible (like instagram when you look at the products of a user).
The problem is that I have the specific list of products in down in the widget tree, so I can't pass them as an argument at the occount page level, without passing them back each step of the way by passing a function as an argument of the widget.
class ProductList extends StatelessWidget {
#override
Widget build(BuildContext context) {
var _products = Provider.of<List<ProductModel>>(context);
return ListView.builder(
itemExtent: 150,
scrollDirection: Axis.horizontal,
itemCount: _products.length,
itemBuilder: (context, index) => Card(
margin: const EdgeInsets.all(5),
child: InkWell(
child: ProductTile(_products[index]),
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ProductDetailList(
productIndex: index,
products: _products,
),
),
),
),
),
);
}
}
Here I use MaterialPageRoute, but would like to keep BottomAppBar visible.
Thank you.
To achieve the functionality you are looking for, you would have to write several switch and break case which can be cumbersome as your use is going to be very basic.
What I would suggest is go for this package
This package provide a persistent bottom navigaton bar which is highly customisable and you could even use salmon like bottom bar without writing a single line of extra code.
I have a bottom navigation bar in my flutter app which is used to show different pages. I need to click on a button in one of the pages to navigate to another which can also be navigated through the bottom navigation bar. To keep the state of the page i have used IndexedStack widget. also i highlight which page i am currently at.
How to do so.
Here is the code.
class IndexPage extends StatefulWidget {
#override
_IndexPageState createState() => _IndexPageState();
}
class _IndexPageState extends State<IndexPage>
with SingleTickerProviderStateMixin {
final ValueNotifier<int> pageNumberNotifier = ValueNotifier<int>(0);
final List<Widget> _widgets = <Widget>[
Page1(),
Page2(),
Page3(),
];
#override
void dispose() {
super.dispose();
pageNumberNotifier.dispose();
}
#override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: pageNumberNotifier,
builder: (BuildContext context, int pageNumber, Widget child) {
return SafeArea(
child: Scaffold(
body: IndexedStack(
index: pageNumberNotifier.value,
children: _widgets,
),
bottomNavigationBar: BottomNavigationBar(
showUnselectedLabels: true,
currentIndex: pageNumber,
onTap: (index) => pageNumberNotifier.value = index,
items: <BottomNavigationBarItem>[
bottomNavigationBarItem(
iconString: 'page1', index: 0, title: 'Page1'),
bottomNavigationBarItem(
iconString: 'page2', index: 1, title: 'Page2'),
bottomNavigationBarItem(
iconString: 'page3', index: 2, title: 'Page3'),
],
),
),
);
});
}
BottomNavigationBarItem bottomNavigationBarItem(
{String iconString, int index, String title}) {
//shows the icons and also highlights the icon of the current page based on the current index.
}
}
Here is the page that contains the button
class Page1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
...
onTap(){
// go to Page3 and also highlight Page3 icon in the bottom navigation bar
}
...
}
}
Use a GlobalKey
GlobalKey<_IndexPageState> key = GlobalKey();
make a function inside _IndexPageState:
void setPage(int page) {
pageNumberNotifier.value = page;
}
call this method from anywhere with:
key.currentState.setPage(0);
I'm working on a project with flutter, I have a bottom navigation bar , this bar allow me to move on different scenes . I would like to remove my bottom navigation bar when i navigate to a detail page, there is any method? I'm trying all the possible solution , but i can't solve the problem, maybe i've implemented wrong my navigation bar !
Some Code:
My HomePage where i implemented the bottom navigation bar and where i call all the others pages:
class homeScreen extends StatefulWidget {
#override
_homeScreenState createState() => _homeScreenState();
}
class _homeScreenState extends State<homeScreen> {
TextEditingController _linkController;
int _currentIndex = 0;
final _pageOptions = [
meetingScreen(),
dashboardScreen(),
notificationScreen(),
profileScreen(),
];
#override
void initState() {
// TODO: implement initState
super.initState();
_linkController = new TextEditingController();
}
#override
Widget build(BuildContext context) {
ScreenUtil.instance = ScreenUtil(width: 1125, height: 2436)..init(context);
return MaterialApp(
title: myUi.myTitle,
theme: myUi.myTheme ,
debugShowCheckedModeBanner: false,
home:Scaffold(
bottomNavigationBar: _buildBottomNavigationBar(),
body: _pageOptions[_currentIndex] ,
) ,
);
}
Widget _buildBottomNavigationBar() {
return BottomNavigationBar(
backgroundColor: Colors.white,
selectedItemColor: Theme.of(context).primaryColor,
//fixedColor: gvar.secondaryColor[gvar.colorIndex],
currentIndex: _currentIndex,
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.explore,size: ScreenUtil().setWidth(80),),
title: new Text("Esplora"),
),
BottomNavigationBarItem(
icon:Icon(Icons.dashboard,size: ScreenUtil().setWidth(80),),
title: new Text("Attività "),
),
BottomNavigationBarItem(
icon:Icon(Icons.notifications,size: ScreenUtil().setWidth(80),),
title: new Text("Notifiche"),
),
BottomNavigationBarItem(
icon:Icon(Icons.person,size: ScreenUtil().setWidth(80),),
title: new Text("Profilo"),
),
],
);
}
}
This is the code i use into the dashboardScreen to push to my detail page:
onTap: () {
//method: navigate to Meeting
Navigator.push(context, MaterialPageRoute(
builder: (BuildContext context) => new meetingScreen()),
);
},
In the new meetingScreen i have some ui , but i cant remove my old bottom navigation bar, i tried implementing a new navigation bar but it isn't displayed.
A partial working solution was to do like so:
Navigator.of(context, rootNavigator: true).pushReplacement(MaterialPageRoute(builder: (context) => new meetingScreen()));
But now I can't do Navigator.pop with the cool animation , because the new page is the root!
Thanks guys for the support !!
To navigate to a different screen without the bottom Navigation bar, you'd need to call Navigator.push() from _homeScreenState. For you to be able to do this, you need to create a NavigatorKey in _homeScreenState and set it in MaterialApp().
final _mainNavigatorKey = GlobalKey<NavigatorState>();
...
MaterialApp(
navigatorKey: _mainNavigatorKey,
...
)
You can then pass the NavigatorKey to your pages. To use _mainNavigatorKey, fetch its current state to be able to invoke push().
navigatorKey.currentState!.push(...)
Here's a sample that I have. Though instead of a bottom Navigation bar, I'm using a Navigation Drawer. The same method applies in this sample.