I have something like this
int _selectedIndex = 0;
final List<Widget> _screens = const [
TopNavigator(),
SearchScreen(),
SettingsScreen(),
SelectedAlbumScreen()
];
void setSelectedScreen(int index) {
setState(() {
_selectedIndex = index;
});
}
BottomNavigationBar(
type: BottomNavigationBarType.fixed,
backgroundColor: Colors.black,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(
icon: Icon(Icons.search), label: 'Search'),
BottomNavigationBarItem(
icon: Icon(Icons.settings), label: 'Settings'),
],
currentIndex: _selectedIndex,
selectedItemColor: Theme.of(context).primaryColor,
unselectedItemColor: Colors.grey,
onTap: (int index) {
setSelectedScreen(index);
},
),
As you can see, I have 4 screens in my list, but only 3 declared in my BottomNavigationBar, so I need to have those 4 screens available to go to them but the number 4 must be hidden for the user so that it can only be accessed by tapping on some button.
When I switch to the screen via code, I get an assertion error.
GestureDetector(
onTap: () {
context.read<HandleBottomNavigationIndex>().setSelectedScreen(3);
}
Failed assertion: line 251 pos 15: '0 <= currentIndex && currentIndex < items.length': is not true.
3
Try below code…
bool showItem = true;
void setSelectedScreen(int index) {
if (index >= 0 && index < _screens.length) {
setState(() {
_selectedIndex = index;
});
}
}
List<BottomNavigationBarItem> get _visibleItems {
final items = <BottomNavigationBarItem>[
const BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
const BottomNavigationBarItem(
icon: Icon(Icons.search),
label: 'Search',
),
const BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: 'Settings',
),
if (showItem)
const BottomNavigationBarItem(
icon: Icon(Icons.album),
label: 'Album',
),
];
return items;
}
And in your build method
final visibleScreens = showItem
? _screens
: _screens.sublist(0, 3);
body: visibleScreens[_selectedIndex],
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
backgroundColor: Colors.black,
items: _visibleItems,
currentIndex: _selectedIndex,
selectedItemColor: Theme.of(context).primaryColor,
unselectedItemColor: Colors.grey,
onTap: (int index) {
setSelectedScreen(index);
},
),
Change showItem true/false as your requirement in setState
2
It seems that by the very nature of the BottomNavigationBar it is impossible to have a number of screens different from the number of items in the BottomNavigationBar.
but I was able to solve it just by creating my own BottomNavigationBar.
here is the code:
item:
import 'package:flutter/material.dart';
class CustomBottomNavigationBarItem {
final IconData icon;
final String label;
const CustomBottomNavigationBarItem(
{required this.icon, required this.label});
}
BottomNavigatonBar:
import 'package:flutter/material.dart';
import 'package:palm_player/presentation/widgets/bottom_navigator/custom_bottom_navigation_bar_item.dart';
class CustomBottomNavigationBar extends StatefulWidget {
final List<CustomBottomNavigationBarItem> items;
final void Function(int index) onTap;
final int selectedIndex;
final Color? selectedItemColor;
final Color? unselectedItemColor;
final Color? backgroundColor;
const CustomBottomNavigationBar(
{super.key,
required this.items,
required this.onTap,
required this.selectedIndex,
this.backgroundColor = Colors.white,
this.selectedItemColor = Colors.black,
this.unselectedItemColor = Colors.grey})
: assert(items.length >= 2);
@override
State<CustomBottomNavigationBar> createState() =>
_CustomBottomNavigationBarState();
}
class _CustomBottomNavigationBarState extends State<CustomBottomNavigationBar> {
@override
Widget build(BuildContext context) {
const double bottomNavigationBarHeight =
kBottomNavigationBarHeight + (kBottomNavigationBarHeight * 0.2);
return Container(
height: bottomNavigationBarHeight,
decoration: BoxDecoration(color: widget.backgroundColor),
padding: const EdgeInsets.only(top: 10),
child: Row(
children: [
const Spacer(),
...widget.items.asMap().entries.map((currentItem) => Expanded(
child: InternalItem(
icon: currentItem.value.icon,
label: currentItem.value.label,
onTap: widget.onTap,
index: currentItem.key,
color: widget.selectedIndex == currentItem.key
? widget.selectedItemColor!
: widget.unselectedItemColor!,
))),
const Spacer()
],
),
);
}
}
class InternalItem extends StatelessWidget {
final int index;
final IconData icon;
final String label;
final Color color;
final void Function(int index) onTap;
const InternalItem(
{super.key,
required this.icon,
required this.label,
required this.onTap,
required this.index,
required this.color});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
onTap(index);
},
child: Column(
children: [
Icon(
icon,
color: color,
),
Text(
label,
style: TextStyle(color: color, fontSize: 11),
)
],
),
);
}
}
implementation:
CustomBottomNavigationBar(
selectedItemColor: Theme.of(context).primaryColor,
selectedIndex: _selectedIndex,
backgroundColor: Colors.black,
items: const <CustomBottomNavigationBarItem>[
CustomBottomNavigationBarItem(
icon: Icons.home, label: 'Home'),
CustomBottomNavigationBarItem(
icon: Icons.search, label: 'Search'),
CustomBottomNavigationBarItem(
icon: Icons.settings, label: 'Settings')
],
onTap: (int index) {
setSelectedScreen(index);
},
),
```