flutterdart

Using navigation bar in unlinked pages


Hi I would like to use/show the bottomnavbar in a page which is not listed in pages list. Screens in pages list works fine. I don't need it for route but only to show it Here is my main page.Example: DetailsPage after clicking something in HomeScreen()

class BottomNavBars extends StatefulWidget {
   const BottomNavBars({super.key});

  @override
  State<BottomNavBars> createState() => _BottomNavBarsState();
  }

  class _BottomNavBarsState extends State<BottomNavBars> {
  int _selectedIndex = 0;
  static const List pages = [
    Screen1(),
    Screen2()

  ];

  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
   }

  @override
  Widget build(BuildContext context) {
    return Scaffold(

      body: Center(child: pages.elementAt(_selectedIndex)),
      bottomNavigationBar: BottomNavigationBar(
        items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: 'Home',
        
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.business),
            label: 'Watch',
            backgroundColor: Colors.green,
          ),    
      
        ],
        currentIndex: _selectedIndex,
     
        onTap: _onItemTapped,
      ),
    );
  }
}```

Solution

  • Based on your question, here’s what I understand:

    1. You have a main screen this screen has ButtomNavigationBar
    2. You want to navigate to a Widget not in the pages List of this BottomNavigationBar
    3. You want to navigate to this Widget using a component (lets say Button in the HomeScreen)
    4. You want the BottomNavigationBar to remain visible on this new widget (DetailsScreen).
    5. You want to show back the main screens (HomeScreen, CartScreen, ProfileScreen) from this widget.

    =>This is a demo video for the provided solution : CustomNavigationBar_Flutter

    If this is correct, I’ll provide this solution. Please Let me know if I misunderstood!

    The solution I’m providing is not ideal but works for your use case. If you meant something else, please let me know!

    Step 1: Define a Model for Each Screen

    class MyScreensModel {
      final String? title;
      final Widget targetWidget;
      final IconData icon;
    
      const MyScreensModel({
        required this.icon,
        required this.targetWidget,
        this.title,
      });
    }
    

    You can define the properties you want in this Model (It is up to you)

    Step 2: Build the Pages Screens

    define the screens you want to display. Lets say:

    it is a HomeScreen(), CartScreen(), ProfileScreen() and DetailsScreen() do not worry about ChangeNotifierProvider

    const TextStyle style = TextStyle(
      fontSize: 20,
      fontWeight: FontWeight.bold,
    );
    
    class CartScreen extends StatelessWidget {
      const CartScreen({super.key});
    
      @override
      Widget build(BuildContext context) {
        return const Text(
          "Cart Screen Content",
          style: style,
        );
      }
    }
    
    class HomeScreen extends StatelessWidget {
      const HomeScreen({super.key});
    
      @override
      Widget build(BuildContext context) {
        return const Text(
          "Home Screen Content",
          style: style,
        );
      }
    }
    
    class ProfileScreen extends StatelessWidget {
      const ProfileScreen({super.key});
    
      @override
      Widget build(BuildContext context) {
        return const Text(
          "Profile Screen Content",
          style: style,
        );
      }
    }
    
    
    
    class DetailsScreen extends StatelessWidget {
      const DetailsScreen({super.key});
    
      @override
      Widget build(BuildContext context) {
        return ChangeNotifierProvider(
          create: (context) => CurrentScreenProvider(),
          child: const Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: <Widget>[
              FlutterLogo(
                size: 200,
              ),
              Text(
                "Details Screen Content",
                style: style,
              ),
            ],
          ),
        );
      }
    }
    

    Step 3: Store Screens in a List

    Then Store it in List<MyScreensModel> and add the data you want

    List<MyScreensModel> navigationScreens = const <MyScreensModel>[
      MyScreensModel(
        icon: Icons.home,
        targetWidget: HomeScreen(),
        title: "Home",
      ),
      MyScreensModel(
        icon: Icons.shopping_bag,
        targetWidget: CartScreen(),
        title: "Cart",
      ),
      MyScreensModel(
        icon: Icons.person,
        targetWidget: ProfileScreen(),
        title: "Profile",
      ),
      MyScreensModel(
        icon: Icons.info,
        targetWidget: DetailsScreen(),
      ),
    ];
    

    Step 4: Use Provider for StateManagement

    In this step we will need a very powerful package for managing this CustomNavigationBar provider

    we will create a Provider to trigger the index of current screen and change this value when the user tap the Item

    class CurrentScreenProvider with ChangeNotifier {
      int _currentScreen = 0;
    
      int get currentScreen => _currentScreen;
    
      // To control the BottomNavigationBar Items
      void selectScreen({
        required int newScreen,
      }) {
        _currentScreen = newScreen;
        notifyListeners();
      }
    
     // To control the DetailsScreen
     void get selectDetialsScreen {
        _currentScreen = 3;
        notifyListeners();
      }
    
    
      bool get isDetails => currentScreen == 3;
    }
    

    the bool isDetails for check if we in the DetailsScreen() or not

    Step 5: Create the Custom Bottom Navigation Bar

    After that we will create our CustomBottomNavigationBar() Widget

    class CustomNavBar extends StatelessWidget {
      const CustomNavBar({
        super.key,
      });
    
      @override
      Widget build(BuildContext context) {
        return Consumer<CurrentScreenProvider>(
          builder: (context, screen, _) {
            return Container(
              height: MediaQuery.sizeOf(context).height * .09,
              decoration: const BoxDecoration(
                borderRadius: BorderRadius.vertical(
                  top: Radius.circular(15),
                ),
                color: Color(0xFFECFFE6),
              ),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceAround,
                children: List.generate(
                  navigationScreens.length - 1,
                  (int index) {
                    return CustomNavbarItemWidget(
                      isSelected: index == screen.currentScreen,
                      targetScreen: navigationScreens[index],
                      onNavTap: () {
                        // Tapping on the Item will update the value of the currentScreen
                        screen.selectScreen(newScreen: index);
                      },
                    );
                  },
                ),
              ),
            );
          },
        );
      }
    }
    

    As you can see we will create a BottomNavigationBar as Row inside a Container you can consider the decoration of the Container as navbr decoration

    Step 6: Create the Bottom Navigation Bar Item

    class CustomNavbarItemWidget extends StatelessWidget {
      const CustomNavbarItemWidget({
        super.key,
        required this.isSelected,
        required this.targetScreen,
        required this.onNavTap,
      });
    
      final bool isSelected;
      final MyScreensModel targetScreen;
    
      final void Function() onNavTap;
    
      @override
      Widget build(BuildContext context) {
        return Container(
          width: MediaQuery.sizeOf(context).width * .16,
          margin: const EdgeInsets.all(5.0),
          decoration: BoxDecoration(
            color: isSelected ? Color(0xFFC7FFD8) : null,
            borderRadius: BorderRadius.circular(10),
          ),
          child: Material(
            color: Colors.transparent,
            child: InkWell(
              borderRadius: BorderRadius.circular(10),
              onTap: onNavTap,
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Icon(targetScreen.icon),
                  if (isSelected) ...{
                    Text(
                      targetScreen.title!,
                      style: const TextStyle(
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                  }
                ],
              ),
            ),
          ),
        );
      }
    }
    

    As you can see you can control the decoration of the selected item using isSelected bool

    And the next widget will be the mainScreen that has the pages Screen

    I will consider that you will show the DetailsScreen() using the floatingActionButton

    class MyScreen extends StatelessWidget {
      const MyScreen({super.key});
    
      Widget _targetWidget({
        required BuildContext context,
      }) {
        final CurrentScreenProvider screen = Provider.of<CurrentScreenProvider>(
          context,
          listen: false,
        );
        int currentScreen = screen.currentScreen;
        switch (currentScreen) {
          case 0:
            {
              return const HomeScreen();
            }
          case 1:
            {
              return const CartScreen();
            }
          case 2:
            {
              return const ProfileScreen();
            }
          case 3:
            {
              return const DetailsScreen();
            }
          default:
            {
              return const HomeScreen();
            }
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return ChangeNotifierProvider(
          create: (context) {
            return CurrentScreenProvider();
          },
          child: Consumer<CurrentScreenProvider>(
            builder: (context, screen, _) {
              return MaterialApp(
                debugShowCheckedModeBanner: false,
                home: Scaffold(
                  appBar: AppBar(
                    automaticallyImplyLeading: false,
                    centerTitle: true,
                    backgroundColor: screen.isDetails ? Colors.cyan : Colors.green,
                    title: Text(
                      screen.isDetails
                          ? "DetailsScreen AppBar Title"
                          : "MainScreen AppBar Title",
                      style: const TextStyle(
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                  ),
                  body: Center(
                    child: _targetWidget(context: context),
                  ),
                  bottomNavigationBar: const CustomNavBar(),
                  floatingActionButton: screen.isDetails
                      ? null
                      : FloatingActionButton(
                          child: const Icon(Icons.info),
                          onPressed: () {
                            screen.selectDetialsScreen;
                          },
                        ),
                ),
              );
            },
          ),
        );
      }
    }
    

    As you can see that the _targetWidget is show the content according to the currentScreen from the CurrentScreenProvider

    Full Code :

    class MyScreensModel {
      final String? title;
      final Widget targetWidget;
      final IconData icon;
    
      const MyScreensModel({
        required this.icon,
        required this.targetWidget,
        this.title,
      });
    }
    
    class CartScreen extends StatelessWidget {
      const CartScreen({super.key});
    
      @override
      Widget build(BuildContext context) {
        return const Text(
          "Cart Screen Content",
          style: style,
        );
      }
    }
    
    class HomeScreen extends StatelessWidget {
      const HomeScreen({super.key});
    
      @override
      Widget build(BuildContext context) {
        return const Text(
          "Home Screen Content",
          style: style,
        );
      }
    }
    
    class ProfileScreen extends StatelessWidget {
      const ProfileScreen({super.key});
    
      @override
      Widget build(BuildContext context) {
        return const Text(
          "Profile Screen Content",
          style: style,
        );
      }
    }
    
    class DetailsScreen extends StatelessWidget {
      const DetailsScreen({super.key});
    
      @override
      Widget build(BuildContext context) {
        return ChangeNotifierProvider(
          create: (context) => CurrentScreenProvider(),
          child: const Text(
            "Details Screen Content",
            style: style,
          ),
        );
      }
    }
    
    const TextStyle style = TextStyle(
      fontSize: 20,
      fontWeight: FontWeight.bold,
    );
    
    List<MyScreensModel> navigationScreens = const <MyScreensModel>[
      MyScreensModel(
        icon: Icons.home,
        targetWidget: HomeScreen(),
        title: "Home",
      ),
      MyScreensModel(
        icon: Icons.shopping_bag,
        targetWidget: CartScreen(),
        title: "Cart",
      ),
      MyScreensModel(
        icon: Icons.person,
        targetWidget: ProfileScreen(),
        title: "Profile",
      ),
      MyScreensModel(
        icon: Icons.info,
        targetWidget: DetailsScreen(),
      ),
    ];
    
    class CurrentScreenProvider with ChangeNotifier {
      int _currentScreen = 0;
    
      int get currentScreen => _currentScreen;
    
      // To control the BottomNavigationBar Items
    
      void selectScreen({
        required int newScreen,
      }) {
        _currentScreen = newScreen;
        notifyListeners();
      }
    
      // To control the DetailsScreen
    
      void get selectDetialsScreen {
        _currentScreen = 3;
        notifyListeners();
      }
    
      bool get isDetails => currentScreen == 3;
    }
    
    class MyScreen extends StatelessWidget {
      const MyScreen({super.key});
    
      Widget _targetWidget({
        required BuildContext context,
      }) {
        final CurrentScreenProvider screen = Provider.of<CurrentScreenProvider>(
          context,
          listen: false,
        );
        int currentScreen = screen.currentScreen;
        switch (currentScreen) {
          case 0:
            {
              return const HomeScreen();
            }
          case 1:
            {
              return const CartScreen();
            }
          case 2:
            {
              return const ProfileScreen();
            }
          case 3:
            {
              return const DetailsScreen();
            }
          default:
            {
              return const HomeScreen();
            }
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return ChangeNotifierProvider(
          create: (context) {
            return CurrentScreenProvider();
          },
          child: Consumer<CurrentScreenProvider>(
            builder: (context, screen, _) {
              return MaterialApp(
                debugShowCheckedModeBanner: false,
                home: Scaffold(
                  appBar: AppBar(
                    automaticallyImplyLeading: false,
                    centerTitle: true,
                    backgroundColor: Colors.green,
                    title: const Text(
                      "Custom Navigation Bar",
                      style: TextStyle(
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                  ),
                  body: Center(
                    child: _targetWidget(context: context),
                  ),
                  bottomNavigationBar: const CustomNavBar(),
                  floatingActionButton: screen.isDetails
                      ? null
                      : FloatingActionButton(
                          child: const Icon(Icons.info),
                          onPressed: () {
                            screen.selectDetialsScreen;
                          },
                        ),
                ),
              );
            },
          ),
        );
      }
    }
    
    class CustomNavBar extends StatelessWidget {
      const CustomNavBar({
        super.key,
      });
    
      @override
      Widget build(BuildContext context) {
        return Consumer<CurrentScreenProvider>(
          builder: (context, screen, _) {
            return Container(
              height: MediaQuery.sizeOf(context).height * .09,
              decoration: const BoxDecoration(
                borderRadius: BorderRadius.vertical(
                  top: Radius.circular(15),
                ),
                color: Color(0xFFECFFE6),
              ),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceAround,
                children: List.generate(
                  navigationScreens.length - 1,
                  (int index) {
                    return CustomNavbarItemWidget(
                      isSelected: index == screen.currentScreen,
                      targetScreen: navigationScreens[index],
                      onNavTap: () {
                        // When the user tap on the Item it will update the value of the currentScreen
                        screen.selectScreen(newScreen: index);
                      },
                    );
                  },
                ),
              ),
            );
          },
        );
      }
    }
    
    class CustomNavbarItemWidget extends StatelessWidget {
      const CustomNavbarItemWidget({
        super.key,
        required this.isSelected,
        required this.targetScreen,
        required this.onNavTap,
      });
    
      final bool isSelected;
      final MyScreensModel targetScreen;
    
      final void Function() onNavTap;
    
      @override
      Widget build(BuildContext context) {
        return Container(
          width: MediaQuery.sizeOf(context).width * .16,
          margin: const EdgeInsets.all(5.0),
          decoration: BoxDecoration(
            color: isSelected ? Colors.cyan.withOpacity(0.2) : null,
            borderRadius: BorderRadius.circular(10),
          ),
          child: Material(
            color: Colors.transparent,
            child: InkWell(
              borderRadius: BorderRadius.circular(10),
              onTap: onNavTap,
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Icon(targetScreen.icon),
                  if (isSelected) ...{
                    Text(
                      targetScreen.title!,
                      style: const TextStyle(
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                  }
                ],
              ),
            ),
          ),
        );
      }
    }