I am creating a dynamic list of BottomNavigationBarItem and assigning it to 'items' of BottomNavigationBar. So based on a condition (here is my case, checks and add one more BottomNavigationBarItem if it is not billed. So the number of Icons displayed change.
Normally for a fixed number of items, ontap provide index of the icon tapped. As the order/sequence of icons change, their index also differs.
Now how do I read the label of selected BottomNavigationBarItem and respond in onTap handler instead of tapped index value ?
(Of course in this particular case, I can add the additional button as last one and done with. Need a better solution.)
List<BottomNavigationBarItem> getNavbarItems() {
List<BottomNavigationBarItem> navItems = [];
navItems.add(
const BottomNavigationBarItem(
icon: Icon(Icons.delete_outline,), label: 'Delete',),
);
if (widget.invStatus.isBilled == 0) {
navItems.add(
const BottomNavigationBarItem(
icon: Icon(Icons.receipt), label: 'Bill it ?'),
);
}
navItems.add(
const BottomNavigationBarItem(
icon: Icon(Icons.category_rounded), label: 'Add a Product !'),
);
return navItems;
}
Thanks.
If I understand you correctly, you can use the following approach to get the "label".
Call:
getNavbarItems()[index].label
And have a button to dynamically toggle isBilled.
Complete example:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Nav(),
),
),
);
}
}
class Nav extends StatefulWidget {
#override
createState() => NavState();
}
class NavState extends State<Nav> {
var _selectedIndex = 0;
var billIt = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Nav'),
),
body: Center(
child: TextButton(
child: Text('Toggle `billIt`'),
onPressed: () {
setState(() {
billIt = !billIt;
});
},
),
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _selectedIndex,
items: getNavbarItems(),
onTap: (int index) {
setState(() {
_selectedIndex = index;
print('label: ${getNavbarItems()[index].label}');
});
},
),
);
}
List<BottomNavigationBarItem> getNavbarItems() {
List<BottomNavigationBarItem> navItems = [];
navItems.add(
const BottomNavigationBarItem(
icon: Icon(
Icons.delete_outline,
),
label: 'Delete',
),
);
if (billIt) {
navItems.add(
const BottomNavigationBarItem(
icon: Icon(Icons.receipt), label: 'Bill it ?'),
);
}
navItems.add(
const BottomNavigationBarItem(
icon: Icon(Icons.category_rounded), label: 'Add a Product !'),
);
return navItems;
}
}
Related
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.
I created a separate file for bottom navigation bar and included the three screens which is to be included in bottom navigation bar .
class _bottomnavscreen extends State<bottomnavscreen> {
int _selectedIndex = 0;
List<Widget> pageList = [home(), create(), profile()];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.add_circle_outline_sharp),
label: 'Create',
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: 'Profile',
),
],
currentIndex: _selectedIndex,
selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
),
body: pageList.elementAt(_selectedIndex),
);
}
I put this bottomnavscreen as the home in main.dart:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: bottomnavscreen(),
);
}
}
But this bootomnavigation widget is seen in my detailedpost screen and comment screen.
detailedpost screen is pushed from home() through Navigation.push()
Comment screen is pushed from postdetails() through
Navigation.push()
How can I hide this bottom navigation widget in my comment screen and detailedpost screen?
This is how I push to detailpost screen from home()
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => detailpost(
body: document['body'],
title: document['title'],
date: document['date'],
)),
);
You can add condition for specific index like this :
class _bottomnavscreen extends State<bottomnavscreen> {
int _selectedIndex = 0;
List<Widget> pageList = [home(), create(), profile()];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: _selectedIndex == 1 ? Container() : BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.add_circle_outline_sharp),
label: 'Create',
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: 'Profile',
),
],
currentIndex: _selectedIndex,
selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
),
body: pageList.elementAt(_selectedIndex),
);
}
You should start a base page from the MyApp and add BottomNavigations in that page only.
Now when you navigate to detailedpost screen and comment screen, the BottomNavigations will not be visible.
you can use the offstage property to hide the bottom navigation bar on specific pages by wrapping it in an Offstage widget and setting the offstage property to true:
import 'package:flutter/material.dart';
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _currentIndex = 0;
final List<Widget> _pages = [ HomePage(), SettingsPage(), ];
#override
Widget build(BuildContext context) {
return Scaffold(
body: _pages[_currentIndex],
bottomNavigationBar: Offstage(
offstage: _currentIndex == 1,
child: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Home'),
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
title: Text('Settings'),
),
],
),
),
);
}
}
is there a way to give the first item of bottom navigtaion bar multi pages ? i tried to make multi widgets on the first item class and call the widgets on button onPressed but the bottom navigation bar disappears,
I'm not sure what you exactly want to implement .but you can try this if it works for you.
class TabsScreen extends StatefulWidget {
static const routeName = 'tab-screen';
#override
_TabsScreenState createState() => _TabsScreenState();
}
class _TabsScreenState extends State<TabsScreen> {
int selectedIndex = 0;
List<Map> tabPages = [
{
'page': FirstScreen(),
},
{
'page': SecondScreen(),
},
{
'page': ThirdScreen(),
}
];
void _selectPage(int index) {
setState(() {
selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: tabPages[selectedIndex]['page'],
bottomNavigationBar: BottomNavigationBar(
selectedItemColor: Colors.orange,
unselectedItemColor: Colors.grey,
currentIndex: selectedIndex,
onTap: _selectPage,
backgroundColor: Colors.grey[300],
items: [
BottomNavigationBarItem(
icon: Icon(
Icons.home,
size: 30,
),
label: 'FirstScreen'),
BottomNavigationBarItem(
icon: Icon(
Icons.restaurant,
size: 30,
),
label: 'SecondScreen'),
BottomNavigationBarItem(
icon: Icon(
Icons.settings,
size: 30,
),
label: 'ThirdScreen'),
],
),
);
}
}
I am working with bottom navigation bar in flutter. I want to refresh every tab when tabs are switched. First I tried to reuse one stateless widget for all the tabs. But it is rerendering pages. My code is as follows:
class _CreateOrderState extends State<CreateOrder> {
int _currentTabIndex = 0;
#override
Widget build(BuildContext context) {
final _kTabPages = <Widget>[
FoodCategory(foodCategory: 'nationalFood'),
FoodCategory(foodCategory: 'fastFood'),
FoodCategory(foodCategory: 'dessert'),
FoodCategory(foodCategory: 'drinks'),
];
final _kBottomNavBarItems = <BottomNavigationBarItem>[
const BottomNavigationBarItem(
icon: Icon(Icons.fastfood_outlined),
label: 'Традиционная',
),
const BottomNavigationBarItem(
icon: Icon(Icons.alarm),
label: 'Фаст Фуд',
),
const BottomNavigationBarItem(
icon: Icon(Icons.food_bank_outlined),
label: 'Дессерты',
),
const BottomNavigationBarItem(
icon: Icon(Icons.emoji_food_beverage),
label: 'Напитки',
),
];
assert(_kTabPages.length == _kBottomNavBarItems.length);
final bottomNavBar = BottomNavigationBar(
items: _kBottomNavBarItems,
currentIndex: _currentTabIndex,
type: BottomNavigationBarType.fixed,
onTap: (int index) {
setState(() => _currentTabIndex = index);
},
);
return WillPopScope(
onWillPop: () => _onWillPop(),
child: Scaffold(
appBar: AppBar(
title: const Text('Создание заказа'),
backgroundColor: Theme.of(context).primaryColor,
actions: [
Container(
child: Stack(
children: [
IconButton(
icon: Icon(Icons.shopping_bag_outlined),
onPressed: () =>
Navigator.pushNamed(context, '/orderReview'),
iconSize: 30,
),
],
),
)
],
),
body: _kTabPages[_currentTabIndex],
// body: IndexedStack(
// index: _currentTabIndex,
// children: _kTabPages,
// ),
bottomNavigationBar: bottomNavBar,
),
);
}
This is my stateless widget:
import 'package:counter/blocs/food/food_bloc.dart';
import 'package:counter/data/repository/food_repository.dart';
import 'package:counter/presentation/widgets/Loading.dart';
import 'package:counter/presentation/widgets/MenuItem.dart';
import 'package:counter/presentation/widgets/network_error.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class FoodCategory extends StatelessWidget {
final String foodCategory;
FoodCategory({#required this.foodCategory});
#override
Widget build(BuildContext context) {
FoodRepository foodRepository = FoodRepository(category: this.foodCategory);
return BlocProvider<FoodBloc>(
create: (BuildContext context) =>
FoodBloc(foodRepository: foodRepository)..add(FoodLoadEvent()),
child: Scaffold(
body: BlocBuilder<FoodBloc, FoodState>(
builder: (context, state) {
if (state is FoodInitial) {
return Text('Initial state');
}
if (state is FoodLoadingState) {
return CustomLoading();
}
if (state is FoodLoadedState) {
return ListView.builder(
itemBuilder: (BuildContext context, index) {
return MenuItem(foodItem: state.loadedFoodItems[index]);
},
itemCount: state.loadedFoodItems.length,
);
} else {
return NetworkErrorWidget();
}
},
),
),
);
}
}
But when I used different widgets for all the tabs, it has started to work properly and refreshed.
final _kTabPages = <Widget>[
NationalFood(foodCategory: 'nationalFood'),
FastFoodScreen(foodCategory: 'fastFood'),
DessertsScreen(foodCategory: 'dessert'),
DrinksScreen(foodCategory: 'drinks'),
];
Inside the initState of your FoodCategory or NationalFood widget, add the function the retrieves your data.
Since you are placing the _kTabPages and _kBottomNavBarItems initialization within the build() method, these variables are assigned new values every time there's a change in state (when you change tabs). This is why the tabs keep re-rendering.
To stop this, place your initialization within the initState(). Something like this:
import 'package:flutter/material.dart';
import 'package:test_flutter_app/test.dart';
class CreateOrder extends StatefulWidget {
#override
_CreateOrderState createState() => _CreateOrderState();
}
class _CreateOrderState extends State<CreateOrder> {
int _currentTabIndex = 0;
List<Widget> _kTabPages;
List<BottomNavigationBarItem> _kBottomNavBarItems;
BottomNavigationBar bottomNavBar;
_updateTabs() {
_kTabPages = <Widget>[
FoodCategory(key: UniqueKey(), foodCategory: 'nationalFood'),
FoodCategory(key: UniqueKey(), foodCategory: 'fastFood'),
FoodCategory(key: UniqueKey(), foodCategory: 'dessert'),
FoodCategory(key: UniqueKey(), foodCategory: 'drinks'),
];
_kBottomNavBarItems = <BottomNavigationBarItem>[
const BottomNavigationBarItem(
icon: Icon(Icons.fastfood_outlined),
label: 'Традиционная',
),
const BottomNavigationBarItem(
icon: Icon(Icons.alarm),
label: 'Фаст Фуд',
),
const BottomNavigationBarItem(
icon: Icon(Icons.food_bank_outlined),
label: 'Дессерты',
),
const BottomNavigationBarItem(
icon: Icon(Icons.emoji_food_beverage),
label: 'Напитки',
),
];
bottomNavBar = BottomNavigationBar(
items: _kBottomNavBarItems,
currentIndex: _currentTabIndex,
type: BottomNavigationBarType.fixed,
onTap: (int index) {
setState(() => _currentTabIndex = index);
},
);
assert(_kTabPages.length == _kBottomNavBarItems.length);
}
#override
void initState() {
_updateTabs();
super.initState();
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async => true,
child: Scaffold(
appBar: AppBar(
title: const Text('Создание заказа'),
backgroundColor: Theme.of(context).primaryColor,
),
body: _kTabPages[_currentTabIndex],
// body: IndexedStack(
// index: _currentTabIndex,
// children: _kTabPages,
// ),
bottomNavigationBar: bottomNavBar,
),
);
}
}
I am really new at flutter. I need a tap on bottomnavigationBar which will open a new page. But where should I use onTap? On Top (body) I have an other Navigation(Tabbarview). Why I dont have an "onTap" inside the BottomnavigationBar
bottomNavigationBar: SnakeNavigationBar(
style: SnakeBarStyle.floating,
backgroundColor: Color(0xffFFFFFF),
snakeColor: Color(0xff3f51b5),
currentIndex: _selectedItemPosition,
onPositionChanged: (index) =>
setState(() => _selectedItemPosition = index),
padding: EdgeInsets.all(4),
items: [
new BottomNavigationBarItem(icon: Icon(Icons.home)),
new BottomNavigationBarItem(icon: Icon(Icons.description)),
new BottomNavigationBarItem(icon: Icon(Icons.people)),
new BottomNavigationBarItem(icon: Icon(Icons.settings)),
new BottomNavigationBarItem(icon: Icon(Icons.battery_unknown)),
],
)));
There's many ways to use a BottomNavigationBar, in your case, that's a library an uses "onPositionChanged" as "onTap" there, not only you can update your variable, but open change the current widget display on screen.
Here a full example to switch between widgets using a BottomNavigationBar:
import 'package:flutter/material.dart';
void main() => runApp(Demo());
class Demo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(home: DemoApp());
}
}
class DemoApp extends StatefulWidget {
#override
_DemoAppState createState() => _DemoAppState();
}
class _DemoAppState extends State<DemoApp> {
Widget _currentWidget = Container();
var _currentIndex = 0;
Widget _homeScreen() {
return Container(
color: Colors.green,
);
}
Widget _settingsScreen() {
return Container(
color: Colors.red,
);
}
#override
void initState() {
super.initState();
_loadScreen();
}
void _loadScreen() {
switch(_currentIndex) {
case 0: return setState(() => _currentWidget = _homeScreen());
case 1: return setState(() => _currentWidget = _settingsScreen());
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: _currentWidget,
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (index) {
setState(() => _currentIndex = index);
_loadScreen();
},
items: [
BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('Home')),
BottomNavigationBarItem(icon: Icon(Icons.settings), title: Text('Settings')),
],
),
);
}
}
The result of this is the following app: