In the world of app development, navigation plays a pivotal role. It's the mechanism that allows users to move between different screens (or pages) of an app. But what happens when the navigation structure of an app becomes complex? This is where the concept of nested navigation comes into play.
Nested navigation, as the name suggests, is a form of navigation where navigators are nested within one another. This is particularly useful when we have a complex app with a large number of screens. Instead of defining all routes in the top-level navigator widget, we can delegate some of the navigation responsibilities to nested navigators. This not only makes our code cleaner and more manageable, but it also provides a better user experience.
Before we delve into the specifics of nested navigation, it's crucial to understand the basics of navigation in Flutter. This includes understanding the role of the Navigator widget, the concept of a navigation stack, the importance of routes, and the function of the back button.
The Navigator widget is a fundamental component in Flutter navigation. It manages a stack of Route objects and provides methods to navigate between these routes. Each route corresponds to a Widget that the Navigator can transition to.
1 // Creating a Navigator widget 2 Navigator( 3 key: _navigatorKey, 4 initialRoute: widget.setupPageRoute, 5 onGenerateRoute: _onGenerateRoute, 6 ); 7
In the code snippet above, we create a Navigator widget. The key attribute is used to identify the Navigator, the initialRoute attribute specifies the first screen to display, and the onGenerateRoute attribute is a function that returns a route based on the route's name.
The navigation stack is a data structure used by the Navigator widget to manage routes. It follows the Last-In-First-Out (LIFO) principle, meaning the last route added to the stack is the first one to be displayed on the screen. When a new route is navigated to, it's pushed onto the stack. When the back button is pressed, the current route is popped from the stack, and the previous route becomes the current one.
Routes are an essential part of navigation in Flutter. They represent a screen or page in an app. Each route has a name, typically represented as a string (for example, '/settings'). These route names are used to navigate between different routes.
1 // Navigating to a new route 2 _navigatorKey.currentState!.pushNamed(routeDeviceSetupSelectDevicePage); 3
In the code snippet above, we navigate to a new route using its name.
The back button plays a crucial role in navigation. It allows users to go back to the previous screen. In Flutter, pressing the back button pops the current route from the navigation stack, causing the Navigator to display the previous route.
1 // Handling the back button press 2 Future<bool> _isExitDesired() async { 3 return await showDialog<bool>( 4 context: context, 5 builder: (context) { 6 return AlertDialog( 7 title: const Text('Are you sure?'), 8 content: const Text( 9 'If you exit device setup, your progress will be lost.'), 10 actions: [ 11 TextButton( 12 onPressed: () { 13 Navigator.of(context).pop(true); 14 }, 15 child: const Text('Leave'), 16 ), 17 TextButton( 18 onPressed: () { 19 Navigator.of(context).pop(false); 20 }, 21 child: const Text('Stay'), 22 ), 23 ], 24 ); 25 }) ?? 26 false; 27 } 28
In the code snippet above, we handle the back button press. When the back button is pressed, an alert dialog pops up to confirm that the user wants to leave the current screen. If the user presses 'Leave', then the current route is popped from the navigation stack. If the user presses 'Stay', then the action is ignored.
Nested navigation, as the name suggests, involves nesting navigators within one another. In simpler terms, it's a navigation pattern where we have a main navigator, often referred to as the root navigator, and one or more child navigators, also known as nested navigators. Each nested navigator manages its own set of routes and navigation stack.
1 // Creating a nested Navigator 2 Navigator( 3 key: _navigatorKey, 4 initialRoute: widget.setupPageRoute, 5 onGenerateRoute: _onGenerateRoute, 6 ); 7
In the code snippet above, we create a nested Navigator. This nested Navigator will manage its own set of routes and navigation stack, independent of the root Navigator.
Implementing nested navigation in your app can provide several benefits:
Before we start implementing nested navigation, we need to set up our development environment. This involves initiating a new file for our nested navigation example, setting up the main sections of the app, and defining the route names for nested navigation.
The first step in implementing nested navigation is to create a new file where we will write our nested navigation code. This file will contain our main app widget and all the necessary widgets for our nested navigation example.
1 // Initiating a new file for the nested navigation example 2 void main() { 3 runApp(MyApp()); 4 } 5 6 class MyApp extends StatelessWidget { 7 8 Widget build(BuildContext context) { 9 return MaterialApp( 10 title: 'Nested Navigation Example', 11 theme: ThemeData( 12 primarySwatch: Colors.blue, 13 ), 14 home: MyHomePage(), 15 ); 16 } 17 } 18
In the code snippet above, we initiate a new file for our nested navigation example. We create a MyApp widget, which is the root widget of our app.
The next step is to set up the main sections of our app. These sections will be managed by our root navigator. Each section will have its own nested navigator to manage its sub-sections.
1 // Setting up the main sections of the app 2 class MyHomePage extends StatelessWidget { 3 4 Widget build(BuildContext context) { 5 return Scaffold( 6 appBar: AppBar( 7 title: Text('Nested Navigation Example'), 8 ), 9 body: Center( 10 child: Column( 11 mainAxisAlignment: MainAxisAlignment.center, 12 children: <Widget>[ 13 Text('Main Sections'), 14 ], 15 ), 16 ), 17 ); 18 } 19 } 20
In the code snippet above, we create a MyHomePage widget. This widget represents the main sections of our app.
The final step in setting up our development environment is to define the route names for our nested navigation. These route names will be used by our nested navigators to navigate between different sub-sections.
1 // Defining the route names for nested navigation 2 const routeHome = '/'; 3 const routeSettings = '/settings'; 4 const routePrefixDeviceSetup = '/setup/'; 5 const routeDeviceSetupStart = '/setup/$routeDeviceSetupStartPage'; 6 const routeDeviceSetupStartPage = 'find_devices'; 7 const routeDeviceSetupSelectDevicePage = 'select_device'; 8 const routeDeviceSetupConnectingPage = 'connecting'; 9 const routeDeviceSetupFinishedPage = 'finished'; 10
In the code snippet above, we define the route names for our nested navigation. These route names will be used by our nested navigators to navigate between different sub-sections.
The app bar is a crucial component in our nested navigation setup. It provides a visual anchor for our app and can house various controls related to navigation.
In the context of nested navigation, the app bar serves several important functions:
Creating an app bar with navigation tabs in Flutter is straightforward. We can use the AppBar widget provided by Flutter and add a TabBar widget to it.
1 // Creating an app bar with navigation tabs 2 AppBar( 3 title: const Text('Nested Navigation Example'), 4 bottom: TabBar( 5 tabs: [ 6 Tab(icon: Icon(Icons.home), text: 'Home'), 7 Tab(icon: Icon(Icons.settings), text: 'Settings'), 8 ], 9 ), 10 ); 11
In the code snippet above, we create an AppBar widget with a TabBar at the bottom. The TabBar contains two tabs: 'Home' and 'Settings'. These tabs can be used to navigate between different sections of our app.
Another essential component in our nested navigation setup is the bottom navigation bar. It provides a secondary navigation mechanism that complements the main navigation provided by the app bar.
The bottom navigation bar serves several important functions in nested navigation:
Creating a bottom navigation bar with icons in Flutter is straightforward. We can use the BottomNavigationBar widget provided by Flutter.
1 // Creating a bottom navigation bar with icons 2 BottomNavigationBar( 3 items: const <BottomNavigationBarItem>[ 4 BottomNavigationBarItem( 5 icon: Icon(Icons.home), 6 label: 'Home', 7 ), 8 BottomNavigationBarItem( 9 icon: Icon(Icons.settings), 10 label: 'Settings', 11 ), 12 ], 13 ); 14
In the code snippet above, we create a BottomNavigationBar widget with two items: 'Home' and 'Settings'. Each item has an icon and a label. These items can be used to navigate between different sections of our app.
After setting up the necessary components, we can now proceed to implement nested navigation in our app. This involves creating nested routes, understanding the role of the parent route, and integrating these elements into our app.
Implementing nested navigation in your app involves creating a nested Navigator widget and defining the routes that this navigator will manage. Each nested Navigator maintains its own stack of routes, allowing it to manage its own set of screens independently of the root Navigator.
1 // Creating a nested Navigator 2 Navigator( 3 key: _navigatorKey, 4 initialRoute: widget.setupPageRoute, 5 onGenerateRoute: _onGenerateRoute, 6 ); 7
In the code snippet above, we create a nested Navigator. This Navigator will manage its own set of routes, independent of the root Navigator.
The next step in implementing nested navigation is to define the routes that our nested Navigator will manage. These routes represent the different screens that the nested Navigator can navigate to.
1 // Defining the routes for the nested Navigator 2 Route _onGenerateRoute(RouteSettings settings) { 3 late Widget page; 4 switch (settings.name) { 5 case routeDeviceSetupStartPage: 6 page = WaitingPage( 7 message: 'Searching for nearby bulb...', 8 onWaitComplete: _onDiscoveryComplete, 9 ); 10 break; 11 case routeDeviceSetupSelectDevicePage: 12 page = SelectDevicePage( 13 onDeviceSelected: _onDeviceSelected, 14 ); 15 break; 16 case routeDeviceSetupConnectingPage: 17 page = WaitingPage( 18 message: 'Connecting...', 19 onWaitComplete: _onConnectionEstablished, 20 ); 21 break; 22 case routeDeviceSetupFinishedPage: 23 page = FinishedPage( 24 onFinishPressed: _exitSetup, 25 ); 26 break; 27 } 28 29 return MaterialPageRoute<dynamic>( 30 builder: (context) { 31 return page; 32 }, 33 settings: settings, 34 ); 35 } 36
In the code snippet above, we define the routes for our nested Navigator using the _onGenerateRoute function. This function takes a RouteSettings object and returns a Route based on the route's name.
In nested navigation, each nested Navigator is typically associated with a parent route in the root Navigator. This parent route serves as the entry point to the nested navigation hierarchy.
The parent route plays a crucial role in nested navigation. It provides a context for the nested Navigator, allowing it to understand its position in the overall navigation hierarchy of the app. This context is important for handling navigation actions correctly, especially when navigating back from the nested Navigator to the root Navigator.
The body widget is a crucial component in our nested navigation setup. It serves as the container for our nested Navigator and its routes.
In the context of nested navigation, the body widget serves several important functions:
Creating the scaffold body for our nested Navigator involves adding a Navigator widget to the body of a Scaffold widget.
1 // Creating the scaffold body for the nested Navigator 2 Scaffold( 3 appBar: _buildFlowAppBar(), 4 body: Navigator( 5 key: _navigatorKey, 6 initialRoute: widget.setupPageRoute, 7 onGenerateRoute: _onGenerateRoute, 8 ), 9 ); 10
In the code snippet above, we create a Scaffold widget and add a Navigator widget to its body. This Navigator will manage its own set of routes, independent of the root Navigator.
The body widget works closely with the navigation stack to manage the transitions between different routes. When a new route is navigated to, the Navigator pushes the route onto the navigation stack and updates the body widget to display the new route. When the back button is pressed, the Navigator pops the current route from the navigation stack and updates the body widget to display the previous route.
Once we have set up our nested navigation structure, we can start navigating inside the app. This involves understanding how to use the nested navigator, the role of the current screen in navigation, and how the app navigates to a new screen.
Navigating inside the app using the nested navigator is similar to using the root navigator. We use the pushNamed method of the nested navigator to navigate to a new route.
1 // Navigating to a new route using the nested navigator 2 _navigatorKey.currentState!.pushNamed(routeDeviceSetupSelectDevicePage); 3
In the code snippet above, we navigate to a new route using the pushNamed method of our nested navigator. This method takes the name of the route as a parameter and navigates to the corresponding route.
The current screen plays a crucial role in navigation. It's the screen that the user is currently interacting with, and it's the screen from which navigation actions are initiated.
In the context of nested navigation, the current screen is the topmost route in the navigation stack of the nested navigator. When a new route is navigated to, it becomes the current screen. When the back button is pressed, the current screen is popped from the navigation stack, and the previous route becomes the current screen.
When the app navigates to a new screen, the following steps are performed:
1 // Navigating to a new screen 2 void _onDeviceSelected(String deviceId) { 3 _navigatorKey.currentState!.pushNamed(routeDeviceSetupConnectingPage); 4 } 5
In the code snippet above, we define a method that navigates to a new screen when a device is selected. This method calls the pushNamed method of our nested navigator to navigate to the connecting page.
State management is a crucial aspect of any app, and it's no different in the context of nested navigation. It involves managing the data that changes over time and affects the behavior of the app.
State management in nested navigation is important for several reasons:
The BuildContext context is a handle to the location of a widget in the widget tree. It's used in many parts of Flutter, including state management.
In the context of state management, the BuildContext context can be used to access data that might be needed to build a widget. For example, it can be used to access the current theme of the app, or to access an InheritedWidget that provides some data.
In nested navigation, the BuildContext context can be used to access the nested navigator. This can be useful for navigating to a new route or for popping the current route from the navigation stack.
1 // Using the BuildContext context to navigate to a new route 2 Navigator.of(context).pushNamed(routeDeviceSetupConnectingPage); 3
In the code snippet above, we use the BuildContext context to access the nested navigator and navigate to a new route.
Nested navigation is a powerful concept in Flutter that allows for more complex and intuitive navigation patterns in your applications. It provides a way to break down your app's navigation into smaller, more manageable parts, each with its own set of routes and navigation stack. This not only makes your code cleaner and more modular but also provides a better user experience.
While implementing nested navigation can be challenging, especially in larger apps, the benefits it offers in terms of modularity, isolation, and flexibility make it a worthwhile endeavor.
Tired of manually designing screens, coding on weekends, and technical debt? Let DhiWise handle it for you!
You can build an e-commerce store, healthcare app, portfolio, blogging website, social media or admin panel right away. Use our library of 40+ pre-built free templates to create your first application using DhiWise.