I am trying to implement nested side navigation but when an item has sub items then the item next to the item with sub items show wrong screen on click. The problem seems to related to index. because the next item is taking the index of sub item of the item which comes before it. I have spent a lot of time on this. But I am not able to solve it. If someone knows how to solve it then please let me know. Thank you. The code is given below:
class _MasterScreenState extends State<MasterScreen> {
int _selectedIndex = 0; // Track the selected index
late UserModel cUserModel;
late UserAppInstitutionModel cSelectedUserAppInstitution;
List<MenuList> currentDestinations = [];
Set<int> visibleSubMenus = {}; // Track visible submenus by index
void getUserData(BuildContext context) {
AuthenticationNotifier authenticationNotifier =
Provider.of<AuthenticationNotifier>(context, listen: false);
cUserModel = authenticationNotifier.getUser();
cSelectedUserAppInstitution =
authenticationNotifier.getSelectedUserAppInstitution();
}
void handleMenuTap(int index) {
setState(() {
final menu = currentDestinations[index];
// If the menu has submenus
if (menu.subMenus != null && menu.subMenus!.isNotEmpty) {
// Toggle the visibility of the submenus
if (visibleSubMenus.contains(index)) {
visibleSubMenus.remove(index); // Collapse submenu
} else {
visibleSubMenus.add(index); // Expand submenu
}
// Set selected index to the main menu
_selectedIndex = index;
} else {
// If it's a main menu item without submenus
_selectedIndex = index; // Select main menu item
visibleSubMenus.clear(); // Clear any visible submenus
}
});
}
void handleSubMenuTap(int mainMenuIndex, int subMenuIndex) {
setState(() {
// Calculate the position of the submenu in the list
int baseIndex = mainMenuIndex + 1; // Start after the main menu
// Count how many submenus are in the main menu
int subMenuCount = currentDestinations[mainMenuIndex].subMenus!.length;
// Set selected index based on submenu position
_selectedIndex = baseIndex + subMenuIndex; // Set selected index based on submenu
});
}
List<SideNavigationBarItem> getSideNavigationBarItem(BuildContext context) {
currentDestinations = destinations.where((menu) => menu.isVisible).toList();
List<SideNavigationBarItem> items = [];
// Iterate over main menu items
for (int i = 0; i < currentDestinations.length; i++) {
final menu = currentDestinations[i];
// Add the main menu item
items.add(SideNavigationBarItem(
icon: menu.icon,
label: menu.label,
));
// Check if the submenu should be visible
if (visibleSubMenus.contains(i) && menu.subMenus != null) {
// Add submenu items right after the main menu item
for (int j = 0; j < menu.subMenus!.length; j++) {
items.add(SideNavigationBarItem(
icon: menu.subMenus![j].icon, // Use submenu icon
label: menu.subMenus![j].label, // Submenu label
));
}
}
}
return items;
}
List<Widget> getScreenNavigationBarItem() {
List<Widget> screens = [];
// Add the main menu screens and their submenu screens
for (var menu in currentDestinations) {
screens.add(menu.screen); // Add main menu screen
if (menu.subMenus != null) {
for (var subMenu in menu.subMenus!) {
screens.add(subMenu.screen); // Add submenu screens
}
}
}
return screens;
}
@override
Widget build(BuildContext context) {
getUserData(context);
return Scaffold(
body: Row(
children: [
// Sidebar for main navigation
SideNavigationBar(
expandable: true,
theme: SideNavigationBarTheme.blue(),
footer: SideNavigationBarFooter(
label: Column(
children: [
Text(
'${cUserModel.name} ${cUserModel.surname}',
style: Theme.of(context).textTheme.headlineSmall,
),
Text(
cUserModel.email,
style: Theme.of(context).textTheme.headlineSmall,
),
Text(
cSelectedUserAppInstitution.roleUserAppInstitution,
style: Theme.of(context).textTheme.headlineSmall,
),
],
),
),
selectedIndex: _selectedIndex,
items: getSideNavigationBarItem(context), // Get menu with submenus
onTap: (index) {
// Calculate the current index of items
int currentIndex = 0; // Track the current index across all items (main and submenus)
for (int i = 0; i < currentDestinations.length; i++) {
final menu = currentDestinations[i];
// If this is the selected main menu
if (currentIndex == index) {
// Handle main menu taps
handleMenuTap(i);
return; // Exit the loop once the right menu is handled
}
currentIndex++; // Move to next menu item
// If this menu has visible submenus, we also need to account for those
if (visibleSubMenus.contains(i) && menu.subMenus != null) {
for (int j = 0; j < menu.subMenus!.length; j++) {
if (currentIndex == index) {
handleSubMenuTap(i, j); // Handle the tap for the submenu item
return;
}
currentIndex++; // Move to next submenu item
}
}
}
},
),
// Expanded area to display the selected content
Expanded(
child: LazyIndexedStack(
index: _selectedIndex, // Use the index to display the selected screen
children: getScreenNavigationBarItem(), // List of all screens
),
)
],
),
);
}
}
It can be done by using separate index variable for main menu items and sub menu items. Such as:
class _MasterScreenState extends State<MasterScreen> {
int _selectedMainMenuIndex = 0; // Track selected main menu index
int? _selectedSubMenuIndex; // Track selected submenu
// rest of your code
void handleMenuTap(int index) {
setState(() {
final menu = currentDestinations[index];
if (menu.subMenus != null && menu.subMenus!.isNotEmpty) {
if (visibleSubMenus.contains(index)) {
visibleSubMenus.remove(index); // Collapse submenu
} else {
visibleSubMenus.add(index); // Expand submenu
}
_selectedMainMenuIndex = index;
_selectedSubMenuIndex = null; // Clear any selected submenu
} else {
_selectedMainMenuIndex = index;
_selectedSubMenuIndex = null;
visibleSubMenus.clear(); // Collapse all submenus
}
});
}
void handleSubMenuTap(int mainMenuIndex, int subMenuIndex) {
setState(() {
_selectedMainMenuIndex = mainMenuIndex;
_selectedSubMenuIndex = subMenuIndex; // Track submenu index
});
}
List<SideNavigationBarItem> getSideNavigationBarItems(BuildContext context) {
currentDestinations = destinations.where((menu) => menu.isVisible).toList();
List<SideNavigationBarItem> items = [];
// Iterate over main menu items
for (int i = 0; i < currentDestinations.length; i++) {
final menu = currentDestinations[i];
// Add main menu item
items.add(SideNavigationBarItem(
icon: menu.icon,
label: menu.label,
));
// Add submenu items if they are visible
if (visibleSubMenus.contains(i) && menu.subMenus != null) {
for (int j = 0; j < menu.subMenus!.length; j++) {
items.add(SideNavigationBarItem(
icon: menu.subMenus![j].icon,
label: menu.subMenus![j].label,
));
}
}
}
return items;
}
Widget getSelectedScreen() {
final mainMenu = currentDestinations[_selectedMainMenuIndex];
if (_selectedSubMenuIndex != null) {
return mainMenu.subMenus![_selectedSubMenuIndex!].screen;
}
return mainMenu.screen;
}
@override
Widget build(BuildContext context) {
AuthenticationNotifier authenticationNotifier =
Provider.of<AuthenticationNotifier>(context, listen: true);
cUserModel = authenticationNotifier.getUser();
cSelectedUserAppInstitution = authenticationNotifier.getSelectedUserAppInstitution();
// Display a loading state while data is being fetched
if (cUserModel == null || cSelectedUserAppInstitution == null) {
return const Center(child: CircularProgressIndicator()); // Show loading spinner until data is available
}
footer: SideNavigationBarFooter(
label: Column(
children: [
Text(
'${cUserModel!.name} ${cUserModel!.surname}', // Safely unwrap nullable values
style: Theme.of(context).textTheme.headlineSmall,
),
Text(
cUserModel!.email, // Safely unwrap nullable value
style: Theme.of(context).textTheme.headlineSmall,
),
Text(
cSelectedUserAppInstitution!.roleUserAppInstitution, // Safely unwrap
style: Theme.of(context).textTheme.headlineSmall,
),
],
),
),
selectedIndex: _selectedSubMenuIndex == null
? _selectedMainMenuIndex
: _selectedMainMenuIndex + 1 + _selectedSubMenuIndex!,
items: getSideNavigationBarItems(context),
onTap: (index) {
int currentIndex = 0;
for (int i = 0; i < currentDestinations.length; i++) {
final menu = currentDestinations[i];
if (currentIndex == index) {
handleMenuTap(i);
return;
}
currentIndex++;
if (visibleSubMenus.contains(i) && menu.subMenus != null) {
for (int j = 0; j < menu.subMenus!.length; j++) {
if (currentIndex == index) {
handleSubMenuTap(i, j);
return;
}
currentIndex++;
}
}
}
},
),
// Expanded area to display the selected content
Expanded(
child: getSelectedScreen(),
),
],
),
);
}
}