flutterdartflutter-navigation

Nested List wrong index with child item collapseable in Flutter


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
            ),
          )
        ],
      ),
    );
  }
}


Solution

  • 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(),
              ),
            ],
          ),
        );
      }
    }