I'm facing an issue of "Multiple GlobalKeys in the Widget tree" while Navigation.
I have a BottomNavigationBar & a Drawer defined in the Scaffold of a Base Screen and in the body parameter of Scaffold I have multiple screens which I'm accessing with BottomNavigationBar. The thing is, I'm accessing the Drawer of the Base Screen from one of the multiple screens by using a GlobalKey, and everything's working fine but when I Navigate to the Base Screen from Another Screen then I get the above-mentioned error.
I have tried a solution of not using a static keyword while defining the key and it solves the error of navigation but then I can't access the Drawer because then I get another error of "method 'openDrawer' was called on null".
This is a separate class where I have defined the Key:
class AppKeys {
final GlobalKey<ScaffoldState> homeKey = GlobalKey<ScaffoldState>();
}
This is the Base Screen:
class Base extends StatefulWidget {
#override
_BaseState createState() => _BaseState();
}
class _BaseState extends State<Base> {
int selectedScreen = 0;
final screens = List<Widget>.unmodifiable([Home(), Cart(), Orders(), Help()]);
AppKeys appKeys = AppKeys();
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: MyDrawer(),
key: appKeys.homeKey,
body: screens[selectedScreen],
bottomNavigationBar: SizedBox(
height: 80,
child: BottomNavigationBar(
onTap: (val) {
setState(() {
selectedScreen = val;
});
},
currentIndex: selectedScreen,
selectedItemColor: AppColor.primary,
elevation: 20.0,
unselectedItemColor: Colors.grey,
showUnselectedLabels: true,
type: BottomNavigationBarType.fixed,
iconSize: 25,
selectedFontSize: 15,
unselectedFontSize: 15,
items: [
BottomNavigationBarItem(
icon: Icon(AnanasIcons.home), title: Text("Home")),
BottomNavigationBarItem(
icon: Icon(AnanasIcons.cart), title: Text("Cart")),
BottomNavigationBarItem(
icon: Icon(AnanasIcons.orders), title: Text("My Orders")),
BottomNavigationBarItem(
icon: Icon(AnanasIcons.help), title: Text("Help")),
],
),
),
);
}
}
This is the Home Screen from where I'm accessing the Drawer:
class Home extends StatelessWidget {
final AppKeys appKeys = AppKeys();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(
AnanasIcons.menu,
color: Colors.black,
),
onPressed: () {
appKeys.homeKey.currentState.openDrawer();
}),
backgroundColor: Theme.of(context).canvasColor,
title: Text("Hi"),
actions: [
IconButton(
icon: Icon(
Icons.person,
color: Colors.black,
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Profile(),
));
})
],
),
body: Container(),
);
}
}
I've found the answer! Instead of pushing the route with the key, we need to remove all the routes from the stack till the screen where we have to go.
Here's the code for that:
Navigator.of(context).popUntil(ModalRoute.withName(Base.routeName));
Related
There are five navigation items in total and the widths are 154,153,154,153,154. I know it's kind of strange but the UI design is like this. As for this I guess I cannot use any Widget about navigator in the flutter lib.
Each item contains an icon and a text as usual but the distance between the two should be set exactly. I don't know if it can be set in flutter widget bottomNavigatorBar.
Commonly, the color of the icon, text and the background will change when selected.
What I have done: I created a widget and add it to the bottom of every pages. As you can imagine, when jumping to a new page, the navigation bar will re-render. That is not the same as we use in our mobile phones.
I have found some articles and blogs, but still can't solve my problem. Is there any reference?
You can use the below code and redirect this page into main.dart and can use test and icon also.
class nav extends StatefulWidget {
#override
_navState createState() => _navState();
}
class _navState extends State<nav> {
int tabIndex = 0;
List<Widget> listScreens;
#override
void initState() {
ScreenUtil.init(width: 375, height: 812);
super.initState();
listScreens = [
Home(),
Treatments(),
Request(),
Appointments(),
Menu(),
];
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: listScreens[tabIndex],
bottomNavigationBar: new Theme(
data: Theme.of(context).copyWith(
canvasColor: Color(0xFF320151),
primaryColor: Color(0xFF320151)),
child: BottomNavigationBar(
backgroundColor: Color(0xFF38095c),
selectedItemColor: Color(0xFFe34fd1),
unselectedItemColor: Color(0xFF510382),
currentIndex: tabIndex,
onTap: (int index) {
setState(() {
tabIndex = index;
});
},
items: [
BottomNavigationBarItem(
title: Text(""),
icon: Container(
width: 154, child: Center(child: Icon(AntDesign.home))),
),
BottomNavigationBarItem(
title: Text(""),
icon: Container(
width: 153,
child: Center(
child: Icon(MaterialCommunityIcons.medical_bag))),
),
BottomNavigationBarItem(
title: Text(""),
icon: Container(
width: 154,
child: Center(child: Icon(Fontisto.share_a))),
),
BottomNavigationBarItem(
title: Text(""),
icon: Container(
width: 153,
child: Center(child: Icon(AntDesign.calendar))),
),
BottomNavigationBarItem(
title: Text(""),
icon: Container(
width: 154,
child: Center(child: Icon(Ionicons.ios_person))),
),
]),
)),
);
}
}
I have an application that contains a bottom navigation bar with three buttons. If I were to navigate to the second page of the navigation bar I am presented with a list of more navigation buttons. The pages that these buttons navigate to do not contain the bottom navigation bar. On these pages I have a back button that I would like to navigate back to the bottom navigation page 2.
I have tried:
() => Navigator.of(context).pushAndRemoveUntil(new MaterialPageRoute(builder: (BuildContext context) => new MyApp()), (Route route) => route == null),),
and:
() => Navigator.of(context).pop(),
When I use the first snippet of code, it take be back to the first page of the navigation bar and when I use second snippet of code, all I get is a black screen.
main.dart
import 'package:flutter/material.dart';
import './pages/page1.dart';
import './pages/page2.dart';
import './pages/page3.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return MyAppState();
}
}
class MyAppState extends State<MyApp> {
int _selectedPage = 0;
final _pageOptions = [
Page1(),
Page2(),
Page3(),
];
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'title',
theme: ThemeData(
brightness: Brightness.light,
primaryColor: Colors.blue,
accentColor: Colors.blueAccent,
backgroundColor: Colors.black12,
textTheme: TextTheme(
title: TextStyle(fontSize: 24.0, height: 1.0, color: Theme.of(context).primaryColor),
subtitle: TextStyle(fontSize: 18.0,height: 1.0, color: Theme.of(context).primaryColor),
body1: TextStyle(fontSize: 12.0, height: 1.1),
),
),
home: Scaffold(
appBar: AppBar(title: Center(child: Text("title", textAlign: TextAlign.center))),
body: _pageOptions[_selectedPage],
bottomNavigationBar: BottomNavigationBar(
currentIndex: _selectedPage,
onTap: (int index) {
setState(() {
_selectedPage = index;
});
},
items:[
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Page 1'),
),
BottomNavigationBarItem(
icon: Icon(Icons.info),
title: Text('Page 2'),
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
title: Text('Page 3'),
),
]
),
),
);
}
}
Now from page 2, I can navigate to page A. When I am on page A and I want to navigate back to Page 2. However I am only able to navigate back to Page 1.
Page A:
import 'package:flutter/material.dart';
import '../../main.dart';
class PageA extends StatelessWidget {
#override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
double height = MediaQuery.of(context).size.height;
double ratio = height/width;
return Scaffold(
appBar: AppBar(
leading: Builder(
builder: (BuildContext context) {
return IconButton(
icon: const Icon(Icons.chevron_left),
iconSize: (0.06 * ratio) * width,
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
onPressed: () => Navigator.of(context).pop(),
);
},
),
title: Text("Page A"),
),
body: Container(
decoration: BoxDecoration(color: Theme.of(context).accentColor),
));
}
}
How do I pass in the _selectedPage / index when navigation back to the previous page?
I use shared_preferences to keep track the current page displayed on my app. This should also help keep track the currently displayed page on web when the page is refreshed as web treats a page refresh as an app restart.
I have a navigation_bar.dart file that handles changing to new pages within my app. Within it, I am using the bottomNavigationBar to build out four different pages based on what tab is currently selected like so:
class NavigationBar extends StatefulWidget {
#override
_NavigationBarState createState() => _NavigationBarState();
}
class _NavigationBarState extends State<NavigationBar> {
int _selectedIndex = 0;
final List<Widget> _pageOptions = <Widget>[
Page1(),
Page2(),
Page3(),
Page4(),
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
String userID =
Provider.of<FirebaseUser>(context, listen: false) != null ? Provider.of<FirebaseUser>(context).uid : 'null';
return MultiProvider(
providers: [StreamProvider<MyUser>.value(value: DatabaseService().streamUser(userID))],
child: Scaffold(
body: IndexedStack(
children: _pageOptions,
index: _selectedIndex,
),
bottomNavigationBar: Theme(
data: Theme.of(context).copyWith(
canvasColor: Color(0xff271037).withOpacity(0.90),
splashColor: Colors.transparent,
),
child: BottomNavigationBar(
currentIndex: _selectedIndex,
onTap: _onItemTapped,
unselectedItemColor: Colors.white,
selectedItemColor: Color(0xff3ADEA7),
type: BottomNavigationBarType.fixed,
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Container(),
title: Icon(FontAwesomeIcons.fire, color: Colors.white),
),
BottomNavigationBarItem(
icon: Container(),
title: Icon(Icons.fastfood, color: Colors.white),
),
BottomNavigationBarItem(
icon: Container(),
title: Icon(Icons.directions_bike, color: Colors.white),
),
BottomNavigationBarItem(
icon: Container(),
title: Icon(Icons.person, color: Colors.white),
)
],
),
),
),
);
}
}
Now, in a different file which is Page3.dart, on that page there is an alert dialog that pops up and when clicked, I want it to navigate to Page4().
Future<void> _showMissingDataDialog(String data) async {
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
title: Text('You have not set your $data yet.'),
actions: <Widget>[
FlatButton(
splashColor: Colors.transparent,
highlightColor: Colors.grey[200],
textColor: Colors.black,
child: const Text('Cancel'),
onPressed: () => Navigator.of(context).pop(),
),
FlatButton(
splashColor: Colors.transparent,
highlightColor: Colors.grey[200],
textColor: Colors.black,
child: Text('Set $data', style: TextStyle(fontWeight: FontWeight.bold)),
onPressed: () {
Navigator.of(context).pop();
// TODO: Redirect to page4() here as if it was tapped on the BottomNavigationBar
})
],
);
},
);
}
How can I have it so that clicking the "Set $data" button would route to Page4()? I want to make it so that the bottomNavigationBar reacts to this as if you tapped on the actual fourth BottomNavigationBarItem item.
Give your Nav Bar a Global Key. I declared this outside of all widgets on my main Dart file.
GlobalKey navBarGlobalKey = GlobalKey(debugLabel: 'bottomAppBar');
bottomNavigationBar: BottomNavigationBar(
key: navBarGlobalKey,
onTap: _onItemTapped,
currentIndex: _selectedIndex,
type: BottomNavigationBarType.fixed,
items: [ ... ]
Then use the global key to call the onTap Method in the onPressed method of your button. You will have to import the other dart file into this page before the global key is available.
final BottomNavigationBar navigationBar = navBarGlobalKey.currentWidget;
initialIndex = 0;
navigationBar.onTap(3); //Starts at index 0, so passing in 3 should do the trick.
I have a fixed BottomNavigationBar with 4 items as described below.
How can I set the color of the selected item?
I've tried with: fixedColor and selectedItemColor but it would't work.
BottomNavigationBar(
type: BottomNavigationBarType.fixed,
fixedColor: Colors.blue,
//selectedItemColor: Colors.blue,
currentIndex: snapshot.data.index,
onTap: _bottomNavBarBloc.pickItem,
items: [
BottomNavigationBarItem(
icon: Icon(
Icons.access_time,
color: Colors.black,
),
),
BottomNavigationBarItem(...),
BottomNavigationBarItem(...),
BottomNavigationBarItem(...),
]);
You can provide a different icon for Active and Normal Bottom Navigation Item.
BottomNavigationBarItem _getNavigationItem(IconData icon, String title) {
return BottomNavigationBarItem(
backgroundColor: Colors.white,
activeIcon: Icon(
icon,
color: Colors.teal,
),
icon: Icon(icon, color: Colors.grey),
title: Text(title),
);
}
There are two types available. fixed or shifting. If we add fixed type , the buttons inside bottom navigation does not show any effect when use click a particular button. It just keep fixed to the bottom navigation. If we add shifting , it will show some kind of cool animation when we click a particular button.
Check this out here
A workaround perhaps would be something like this though:
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
home: new MyHomePage(),
));
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int currentIndex = 0;
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: Text("StackoverFlow"),
),
body: Container(),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
currentIndex: currentIndex,
onTap: (index) {
setState(() {
currentIndex = index;
});
},
items: [
BottomNavigationBarItem(
icon: Icon(
Icons.access_time,
color: currentIndex == 0? Colors.blue:Colors.black,
),
title: Container(),
),
BottomNavigationBarItem(
icon: Icon(
Icons.access_time,
color: currentIndex == 1? Colors.blue:Colors.black,
),
title: Container(),
),
]),
);
}
}
I work on flutter and I make an app with 5 tabs, using a BottomNavigationBar, who change the currently displayed content.
When I tap on a tab, the content updates to the new content, but the tabs icon doesn't change.
I have tried to change the BottomNavigationBarType but that changes nothing...
here is base page widget:
class Home extends StatefulWidget {
Home({Key key}) : super(key: key);
#override
_Home createState() => _Home();
}
class _Home extends State<Home> {
int _selectedIndex = 0;
static List<Widget> _widgetOptions = <Widget>[
HomePage(),
CreateTrain(),
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
print(_selectedIndex);
return Scaffold(
body: _widgetOptions.elementAt(_selectedIndex),
bottomNavigationBar: BottomNavigationBar(
currentIndex: 0, // this will be set when a new tab is tapped
items: [
BottomNavigationBarItem(
icon: new Icon(Icons.home),
title: new Text('Home'),
),
BottomNavigationBarItem(
icon: new Icon(Icons.assignment),
title: new Text('Training'),
),
BottomNavigationBarItem(
icon: new Icon(Icons.play_arrow),
title: new Text('start'),
),
BottomNavigationBarItem(
icon: new Icon(Icons.insert_chart),
title: new Text('Stats'),
),
BottomNavigationBarItem(
icon: Icon(Icons.person), title: Text('Profile'))
],
selectedFontSize: 12,
unselectedFontSize: 12,
selectedItemColor: Colors.amber[800],
unselectedItemColor: Colors.grey[500],
showUnselectedLabels: true,
type: BottomNavigationBarType.fixed,
onTap: _onItemTapped,
),
);
}
}
Here is HomePage widget:
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: true,
title: const Text(
'Home',
style: TextStyle(
color: Colors.black,
fontSize: 30,
),
),
backgroundColor: _bgColor,
),
body: Text('data'),
);
}
}
Thanks for your help.
In your code
currentIndex: 0, // this will be set when a new tab is tapped
This should be
currentIndex: _selectedIndex, // this will be set when a new tab is tapped