flutter bottomNavigationBar not changing screens - flutter

I'm new to flutter, trying to implement BottomNavigationBar in a demo app. Bottom bar does show in app but can't change the current item on tapping, neither does the screen changes.
I've tried various methods to load screens but seems like the onTap function is not getting called.
Here is my code.
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
final Size screenSize = MediaQuery.of(context).size;
int _currentTab = 0;
Widget body() {
switch (_currentTab) {
case 0:
return FeedScreen();
case 1:
return SearchScreen();
case 2:
return ProfileScreen();
default:
return FeedScreen();
}
}
return Scaffold(
bottomNavigationBar: BottomNavigationBar(
onTap: (newValue) {
setState(() {
_currentTab = newValue;
});
},
currentIndex: _currentTab,
items: [
BottomNavigationBarItem(
icon: ImageIcon(
AssetImage('images/home.png'),
),
title: SizedBox.shrink(),
),
BottomNavigationBarItem(
icon: ImageIcon(
AssetImage('images/search.png'),
),
title: SizedBox.shrink(),
),
BottomNavigationBarItem(
icon: CircleAvatar(
radius: 15,
backgroundImage: AssetImage('images/avatar.png'),
),
title: SizedBox.shrink(),
)
],
),
appBar: PreferredSize(
preferredSize: Size(screenSize.width, 50.0),
child: CustomAppBar(),
),
body: body(),
);
}
}

your int _currentTab = 0; is inside build method. put it outside.
everytime you trigger the setState, _currentTab will always be equal to 0 which is the FeedScreen because the variable is inside the build method

Try the below code!
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int _currentTab = 0;
#override
Widget build(BuildContext context) {
final Size screenSize = MediaQuery.of(context).size;
Widget body() {
switch (_currentTab) {
case 0:
return FeedScreen();
case 1:
return SearchScreen();
case 2:
return ProfileScreen();
default:
return FeedScreen();
}
}
return Scaffold(
bottomNavigationBar: BottomNavigationBar(
onTap: (newValue) {
setState(() {
_currentTab = newValue;
});
},
currentIndex: _currentTab,
items: [
BottomNavigationBarItem(
icon: ImageIcon(
AssetImage('images/home.png'),
),
title: SizedBox.shrink(),
),
BottomNavigationBarItem(
icon: ImageIcon(
AssetImage('images/search.png'),
),
title: SizedBox.shrink(),
),
BottomNavigationBarItem(
icon: CircleAvatar(
radius: 15,
backgroundImage: AssetImage('images/avatar.png'),
),
title: SizedBox.shrink(),
)
],
),
appBar: PreferredSize(
preferredSize: Size(screenSize.width, 50.0),
child: CustomAppBar(),
),
body: body(),
);
}
}
write int _currentTab = 0 in outside of build method, because when you update newValue into _currentTab also setState reassign 0 value.

Related

BottomNavigationBarItem - Unable to link widgets

I need to have the transactions screen and the categories screen widgets as BottomNavigationBarItems
However, i receive this error
The method 'Transactions' isn't defined for the type 'HomeState'. Try correcting the name of an existing method, or defining a method named 'Transactions'.
lib\screens\home.dart
import 'package:demo_app/screens/categories.dart';
import 'package:demo_app/screens/transactions.dart';
import 'package:flutter/material.dart';
class Home extends StatefulWidget{
const Home({super.key});
#override
State<Home> createState() => HomeState();
}
class HomeState extends State<Home> {
List<Widget> widgetOptions = [Transactions(), Categories()];
int selectedIndex = 0;
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Welcome to Flutter',
home: Scaffold(
appBar: AppBar(
title: const Text('Logged in!'),
),
body: widgetOptions.elementAt(selectedIndex),
bottomNavigationBar: BottomAppBar(
shape: const CircularNotchedRectangle(),
notchMargin: 4,
child: BottomNavigationBar(
backgroundColor: Theme.of(context).primaryColor.withAlpha(0),
elevation: 0,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.account_balance_wallet),
label: 'Transactions'),
BottomNavigationBarItem(
icon: Icon(Icons.category),
label: 'Categories'),
BottomNavigationBarItem(
icon: Icon(Icons.logout),
label: 'Log out'),
],
currentIndex: selectedIndex,
onTap: onItemTapped,
)
)
),
);
}
void onItemTapped(int index){
if(index == 2){
}else{
setState((){
selectedIndex = index;
});
}
}
}
Simply add SizedBox() to widgetOptions list.
List<Widget> widgetOptions = [Transactions(), Categories(), SizedBox()];
Actually the code in the below had issues
screens/categories.dart';
screens/transactions.dart';
lib\screens\home.dart
Solved those and the issue is no longer there.

Flutter: Change Bottom Navigation Bar Items on real time

I am trying to see how I can change the items in BottomNavigationBar within a DefaultTabController on real time but came to no avail.
Basically these are the scenario:
Non-admin Tab Bar: [Screen A and B]
Admin Tab Bar: [Screen C, D and B]
Within screen B, there is a button to change from Admin to non-admin view or vice versa.
Thank you in advance.
Try this, it works. But, I would advise using a Provider rather than passing a WidgetState as an argument to any function or to any Widget.
List<Widget> nonAdminWidgets(_BottomNavScaffoldState parent) {
return <Widget>[
ProfilePage(parent),
ClothesPage(),
ColorsPage(),
];
}
List<Widget> adminWidgets(_BottomNavScaffoldState parent) {
return <Widget>[
ProfilePage(parent),
IdeasPage(),
];
}
int _currentIndex = 0;
bool isAdmin = true;
List<dynamic> nonAdminNavBars = [
BottomNavigationBarItem(
title: Text('Profile'),
icon: Icon(Icons.face_rounded),
),
BottomNavigationBarItem(
title: Text('Clothes'),
icon: Icon(Icons.design_services_rounded),
),
BottomNavigationBarItem(
title: Text('Colors'),
icon: Icon(Icons.colorize_rounded),
),
];
List<dynamic> adminNavBars = [
BottomNavigationBarItem(
title: Text('Profile'),
icon: Icon(Icons.face_rounded),
),
BottomNavigationBarItem(
title: Text('Ideas'),
icon: Icon(Icons.lightbulb_outline_rounded),
),
];
class BottomNavScaffold extends StatefulWidget {
#override
_BottomNavScaffoldState createState() => _BottomNavScaffoldState();
}
class _BottomNavScaffoldState extends State<BottomNavScaffold> {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: isAdmin ? adminWidgets(this)[_currentIndex] : nonAdminWidgets(this)[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
currentIndex: _currentIndex,
backgroundColor: Colors.orangeAccent,
selectedItemColor: Colors.white,
onTap: (value) {
_currentIndex = value;
setState(() {});
},
items: isAdmin ? adminNavBars : nonAdminNavBars,
),
);
}
}
class ClothesPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Card(
color: Colors.white,
child: Padding(
padding: EdgeInsets.all(10),
child: Text(
"Clothes",
),
),
);
}
}
class ColorsPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Card(
color: Colors.white,
child: Padding(
padding: EdgeInsets.all(10),
child: Text(
"Colors",
),
),
);
}
}
class IdeasPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Card(
color: Colors.white,
child: Padding(
padding: EdgeInsets.all(10),
child: Text(
"Ideas",
),
),
);
}
}
class ProfilePage extends StatelessWidget {
_BottomNavScaffoldState parent;
ProfilePage(this.parent);
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
isAdmin = !isAdmin;
_currentIndex = 0;
parent.setState(() {});
},
child: Text("Change"),
);
}
}

how to hide Bottom Navigation Bar on new screen in flutter?

Just like here when I click on the timer, the Bottom navigation bar was disappeared. I want to implement the same thing on flutter. Whenever I click on Bottom Navigation Bar Item, for the new screen the Bottom Navigation Bar should not appear.
Here is my code. My Bottom Navigation Bar has four items and I want to hide the bottom navigation bar when I route to a new screen.
class MyFeedScreen extends StatefulWidget {
#override
_MyFeedScreenState createState() => _MyFeedScreenState();
}
class _MyFeedScreenState extends State<MyFeedScreen> {
int _bottomNavIndex = 0;
Widget pageCaller(int index) {
switch (index) {
case 0:
{
return Category();
}
case 1:
{
return Feed();
}
case 3:
{
return Settings();
}
}
}
#override
Widget build(BuildContext context) {
SizeConfig().init(context);
return Scaffold(
body: pageCaller(_bottomNavIndex),
bottomNavigationBar: BottomNavigationBar(
backgroundColor: klogoBlue,
selectedItemColor: Color(0xfff5f5f5),
unselectedItemColor: Color(0xfff5f5f5),
selectedFontSize: 12.0,
type: BottomNavigationBarType.fixed,
currentIndex: _bottomNavIndex,
onTap: (index) {
setState(() {
_bottomNavIndex = index;
});
},
items: [
BottomNavigationBarItem(
icon: Padding(
padding:
EdgeInsets.only(top: SizeConfig.blockSizeVertical * 0.60),
child: Icon(Icons.category),
),
title: Padding(
padding: EdgeInsets.symmetric(
vertical: SizeConfig.blockSizeVertical * 0.60),
child: Text('Category'),
),
),
BottomNavigationBarItem(
icon: Padding(
padding:
EdgeInsets.only(top: SizeConfig.blockSizeVertical * 0.60),
child: Icon(FontAwesomeIcons.newspaper),
),
title: Padding(
padding: EdgeInsets.symmetric(
vertical: SizeConfig.blockSizeVertical * 0.60),
child: Text('My Feed'),
),
),
BottomNavigationBarItem(
icon: Padding(
padding:
EdgeInsets.only(top: SizeConfig.blockSizeVertical * 0.60),
child: Icon(Icons.refresh),
),
title: Padding(
padding: EdgeInsets.symmetric(
vertical: SizeConfig.blockSizeVertical * 0.60),
child: Text('Refresh'),
),
),
BottomNavigationBarItem(
icon: Padding(
padding:
EdgeInsets.only(top: SizeConfig.blockSizeVertical * 0.60),
child: Icon(Icons.settings),
),
title: Padding(
padding: EdgeInsets.symmetric(
vertical: SizeConfig.blockSizeVertical * 0.60),
child: Text('Settings'),
),
),
],
),
);
}
}
You can try this method
Navigator.of(context, rootNavigator: true).push(MaterialPageRoute(
builder: (_) => NewScreen(),
),
);
You can copy paste run full code below
You can check onTap index and do Navigator.push for specific button
code snippet
void _onItemTapped(int index) {
if (index != 2) {
setState(() {
_bottomNavIndex = index;
});
print(_bottomNavIndex);
} else {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => Settings()),
);
}
}
Widget pageCaller(int index) {
switch (index) {
case 0:
{
return Category();
}
case 1:
{
return Feed();
}
}
}
working demo
full code
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
/// This Widget is the main application widget.
class MyApp extends StatelessWidget {
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: MyStatefulWidget(),
);
}
}
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({Key key}) : super(key: key);
#override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int _bottomNavIndex = 0;
void _onItemTapped(int index) {
if (index != 2) {
setState(() {
_bottomNavIndex = index;
});
print(_bottomNavIndex);
} else {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => Settings()),
);
}
}
Widget pageCaller(int index) {
switch (index) {
case 0:
{
return Category();
}
case 1:
{
return Feed();
}
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
),
body: Center(
child: pageCaller(_bottomNavIndex),
),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Category'),
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
title: Text('Feed'),
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
title: Text('Settings'),
),
],
currentIndex: _bottomNavIndex,
selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
),
);
}
}
class Category extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text("Category"),
),
);
}
}
class Feed extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text("Feed"),
),
);
}
}
class Settings extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Settings'),
),
body: Center(
child: Text("Settings"),
),
);
}
}

Flutter custom AppBar on one page

I am developing Flutter application with AppBar and BottomNavigationBar. Here is the code:
class MainPage extends StatefulWidget {
final MainModel model;
MainPage(this.model);
#override
State<StatefulWidget> createState() {
return _MainPageState();
}
}
class _MainPageState extends State<MainPage> {
int _selectedIndex = 0;
Widget _openSelectedPage() {
switch(_selectedIndex) {
case 0:
return HomePage(widget.model);
case 1:
return CapturePage(widget.model);
case 2:
return MealsPage(widget.model);
default:
return HomePage(widget.model);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('My App'),
),
body: _openSelectedPage(),
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Home'),
),
BottomNavigationBarItem(
icon: Icon(Icons.camera_alt),
title: Text('Capture'),
),
BottomNavigationBarItem(
icon: Icon(Icons.format_list_bulleted),
title: Text('Meals'),
),
],
currentIndex: _selectedIndex,
onTap: (int index) {
setState(() {
_selectedIndex = index;
});
}
),
);
}
}
How can I have custom AppBar on MealsPage? If I add another Scaffold in build method of MealsPage, then it creates two AppBars, which is not what I want. I only want to have one AppBar, the one that I define in MealsPage.
class MainPage extends StatefulWidget {
final MainModel model;
MainPage(this.model);
#override
State<StatefulWidget> createState() {
return _MainPageState();
}
}
class _MainPageState extends State<MainPage> {
int _selectedIndex = 0;
Widget _openSelectedPage() {
switch (_selectedIndex) {
case 0:
return HomePage(widget.model);
case 1:
return CapturePage(widget.model);
case 2:
return MealsPage(widget.model);
default:
return HomePage(widget.model);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: _selectedIndex == 2 ? null : AppBar(title: Text('My App')),
body: _openSelectedPage(),
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Home'),
),
BottomNavigationBarItem(
icon: Icon(Icons.camera_alt),
title: Text('Capture'),
),
BottomNavigationBarItem(
icon: Icon(Icons.format_list_bulleted),
title: Text('Meals'),
),
],
currentIndex: _selectedIndex,
onTap: (int index) {
setState(() {
_selectedIndex = index;
});
},
),
);
}
}

How to use BottomNavigationBar with Navigator?

The Flutter Gallery example of BottomNavigationBar uses a Stack of FadeTransitions in the body of the Scaffold.
I feel it would be cleaner (and easier to animate) if we could switch pages by using a Navigator.
Are there any examples of this?
int index = 0;
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Stack(
children: <Widget>[
new Offstage(
offstage: index != 0,
child: new TickerMode(
enabled: index == 0,
child: new MaterialApp(home: new YourLeftPage()),
),
),
new Offstage(
offstage: index != 1,
child: new TickerMode(
enabled: index == 1,
child: new MaterialApp(home: new YourRightPage()),
),
),
],
),
bottomNavigationBar: new BottomNavigationBar(
currentIndex: index,
onTap: (int index) { setState((){ this.index = index; }); },
items: <BottomNavigationBarItem>[
new BottomNavigationBarItem(
icon: new Icon(Icons.home),
title: new Text("Left"),
),
new BottomNavigationBarItem(
icon: new Icon(Icons.search),
title: new Text("Right"),
),
],
),
);
}
You should keep each page by Stack to keep their state.
Offstage stops painting, TickerMode stops animation.
MaterialApp includes Navigator.
Output:
Code:
int _index = 0;
#override
Widget build(BuildContext context) {
Widget child;
switch (_index) {
case 0:
child = FlutterLogo();
break;
case 1:
child = FlutterLogo(colors: Colors.orange);
break;
case 2:
child = FlutterLogo(colors: Colors.red);
break;
}
return Scaffold(
body: SizedBox.expand(child: child),
bottomNavigationBar: BottomNavigationBar(
onTap: (newIndex) => setState(() => _index = newIndex),
currentIndex: _index,
items: [
BottomNavigationBarItem(icon: Icon(Icons.looks_one), title: Text("Blue")),
BottomNavigationBarItem(icon: Icon(Icons.looks_two), title: Text("Orange")),
BottomNavigationBarItem(icon: Icon(Icons.looks_3), title: Text("Red")),
],
),
);
}
Here is an example how you can use Navigator with BottomNavigationBar to navigate different screen.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
// This navigator state will be used to navigate different pages
final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
int _currentTabIndex = 0;
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Navigator(key: _navigatorKey, onGenerateRoute: generateRoute),
bottomNavigationBar: _bottomNavigationBar(),
),
);
}
Widget _bottomNavigationBar() {
return BottomNavigationBar(
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text("Home"),
),
BottomNavigationBarItem(
icon: Icon(Icons.account_circle), title: Text("Account")),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
title: Text("Settings"),
)
],
onTap: _onTap,
currentIndex: _currentTabIndex,
);
}
_onTap(int tabIndex) {
switch (tabIndex) {
case 0:
_navigatorKey.currentState.pushReplacementNamed("Home");
break;
case 1:
_navigatorKey.currentState.pushReplacementNamed("Account");
break;
case 2:
_navigatorKey.currentState.pushReplacementNamed("Settings");
break;
}
setState(() {
_currentTabIndex = tabIndex;
});
}
Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case "Account":
return MaterialPageRoute(builder: (context) => Container(color: Colors.blue,child: Center(child: Text("Account"))));
case "Settings":
return MaterialPageRoute(builder: (context) => Container(color: Colors.green,child: Center(child: Text("Settings"))));
default:
return MaterialPageRoute(builder: (context) => Container(color: Colors.white,child: Center(child: Text("Home"))));
}
}
}
Here is example:
int _currentIndex = 0;
Route<Null> _getRoute(RouteSettings settings) {
final initialSettings = new RouteSettings(
name: settings.name,
isInitialRoute: true);
return new MaterialPageRoute<Null>(
settings: initialSettings,
builder: (context) =>
new Scaffold(
body: new Center(
child: new Container(
height: 200.0,
width: 200.0,
child: new Column(children: <Widget>[
new Text(settings.name),
new FlatButton(onPressed: () =>
Navigator.of(context).pushNamed(
"${settings.name}/next"), child: new Text("push")),
],
))
),
bottomNavigationBar: new BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (value) {
final routes = ["/list", "/map"];
_currentIndex = value;
Navigator.of(context).pushNamedAndRemoveUntil(
routes[value], (route) => false);
},
items: [
new BottomNavigationBarItem(
icon: new Icon(Icons.list), title: new Text("List")),
new BottomNavigationBarItem(
icon: new Icon(Icons.map), title: new Text("Map")),
]),
));
}
#override
Widget build(BuildContext context) =>
new MaterialApp(
initialRoute: "/list",
onGenerateRoute: _getRoute,
theme: new ThemeData(
primarySwatch: Colors.blue,
),
);
You can set isInitialRoute to true and pass it to MaterialPageRoute. It will remove pop animation.
And to remove old routes you can use pushNamedAndRemoveUntil
Navigator.of(context).pushNamedAndRemoveUntil(routes[value], (route) => false);
To set current page you can have a variable in your state _currentIndex and assign it to BottomNavigationBar:
Glad You asked, I experimented with this a couple of months back and tried to simplify this through a blog post. I won't be able to post the complete code here since it is pretty long, But I can certainly link all the resources to clarify it.
Everything about the BottomNavigationBar in flutter
complete sample code
Dartpad demo
If you prefer you can also depend on this package https://pub.dev/packages/navbar_router
Here's the resulting output of what the article helps you build
Navigator.of(context).pushNamedAndRemoveUntil(
routes[value], (route) => true);
I had to use true to enable back button.
NB: I was using Navigator.pushNamed() for navigation.
This is the code I am using in my project. If you try to avoid page viewer so you can try this
import 'package:flutter/material.dart';
class Dashboard extends StatefulWidget {
const Dashboard({Key? key}) : super(key: key);
#override
State<Dashboard> createState() => _DashboardState();
}
class _DashboardState extends State<Dashboard> {
int _selectedIndex = 0;
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Sample'),
),
body: SingleChildScrollView(
child: Column(
children: [
if (_selectedIndex == 0)
// you can call custom widget here
Column(
children: const [
Text("0"),
],
)
else if (_selectedIndex == 1)
Column(
children: const [
Text("1"),
],
)
else
Column(
children: const [
Text("2"),
],
),
],
),
),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.headphones),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'Business',
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: 'School',
),
],
currentIndex: _selectedIndex,
selectedItemColor: Colors.amber[800],
unselectedItemColor: Colors.grey,
onTap: _onItemTapped,
),
);
}
}
Happy Coding