Flutter App Development

10 min

10 min

Ashutosh Agarwal

Published on Sep 11, 2023

Creating Flutter bottom navigation bar with routes

Navigation is a cornerstone of mobile app development, serving as the bridge between an app's functionality and its users. In this fast-paced digital era, delivering a seamless and intuitive navigation experience has become more critical than ever.

Flutter, the versatile cross-platform framework, has recognized this need and offers an elegant solution through its Bottom Navigation Bar, a user-friendly component commonly used in mobile apps. However, as apps grow in complexity, so do the requirements for navigation. Enter stateful navigation and responsive user interfaces.

Flutter's Bottom Navigation Bar is an industry-standard user experience pattern. It places navigation tabs at the bottom of the screen, making it effortlessly accessible to users, within their natural thumb zone. While this navigation pattern excels in simplifying user journeys, it faces challenges when dealing with complex app structures.

This article is your guide to mastering stateful nested navigation in Flutter, utilizing the GoRouter package. We will dive deep into the significance of stateful navigation and the necessity of responsive user interfaces, addressing real-world navigation challenges.

By the end, you'll be well-equipped to implement stateful nested navigation efficiently, ensuring a delightful user experience in your Flutter applications. Let's embark on this journey to unlock the power of Flutter's GoRouter and take your mobile app development to new heights.


Understanding Stateful Nested Navigation

Stateful nested navigation is a navigation approach that goes beyond the traditional linear navigation structure. In this paradigm, the app's navigation is organized into multiple independent navigation stacks, often referred to as branches. Each branch operates as a separate hierarchy of screens, allowing for isolated state management and navigation flow.


Concept of Stateful Nested Navigation:

  • Branches and Stacks: Stateful nested navigation involves creating branches or stacks of routes, each with its own navigator. This architecture enables the app to manage different sections or features independently.

  • Seamless User Journeys: Stateful nested navigation improves the user experience by ensuring seamless transitions between different sections of the app. Users can navigate within a specific branch without affecting the state of other branches.


Challenges in Flutter Implementation:

  • Complexity Management: Implementing stateful nested navigation in Flutter can be challenging due to the need to manage multiple navigation stacks. Each branch may have its set of routes, making it essential to keep the code organized.

  • State Preservation: Maintaining the state of each branch, especially when switching between branches or tabs, requires careful consideration. It's vital to ensure that the user doesn't lose their progress or data.

  • Responsive UI: Adapting the user interface to provide a consistent experience on different screen sizes can be complex. Flutter's responsive design principles must be applied effectively.

Here's a code snippet illustrating the concept of stateful nested navigation using the GoRouter package in Flutter:

```dart
final goRouter = GoRouter(
  initialLocation: '/home', // Initial branch
  routes: [
    // Define branches with their routes
    StatefulShellRoute(
      builder: (context, state, navigationShell) {
        // Create the UI shell for a branch
        return Scaffold(
          body: navigationShell, // Include the branch's navigator
        );
      },
      branches: [
        StatefulShellBranch(
          routes: [
            GoRoute(
              path: '/home',
              builder: (context, state) => HomeScreen(),
            ),
            GoRoute(
              path: '/details',
              builder: (context, state) => DetailsScreen(),
            ),
          ],
        ),
        // Define more branches as needed
      ],
    ),
    // Define additional top-level routes
  ],
);
```

This code snippet demonstrates the creation of branches and how each branch can have its routes, providing a clear structure for stateful nested navigation.


Leveraging GoRouter for Stateful Nested Navigation

GoRouter is a robust routing package designed for Flutter, offering powerful features for managing complex navigation structures. When it comes to implementing stateful nested navigation, GoRouter provides the necessary tools and APIs to streamline the process.


Introduction to GoRouter

  • Robust Routing Solution: GoRouter is a Flutter package known for its flexibility and efficiency in handling navigation within applications. It goes beyond simple page-to-page navigation.

  • StatefulShellRoute UI Shell Management: StatefulShellRoute is a key component of GoRouter, responsible for managing the UI shell of each navigation branch. It defines the overarching structure of a branch, encompassing all its sub-routes.

  • StatefulShellBranch Isolated Navigation Stacks: StatefulShellBranch is used within StatefulShellRoute to create isolated navigation stacks or branches. Each branch can represent a different section or feature within the app.

  • Routing Hierarchy with GoRouter Structure: GoRouter allows you to define a hierarchical routing structure, making it easy to organize and manage navigation. The hierarchy is composed of routes and branches.

Here's a code snippet illustrating the use of StatefulShellRoute and StatefulShellBranch in GoRouter:

```dart
final goRouter = GoRouter(
  initialLocation: '/home', // Initial branch
  routes: [
    StatefulShellRoute(
      builder: (context, state, navigationShell) {
        // Create the UI shell for a branch
        return Scaffold(
          body: navigationShell, // Include the branch's navigator
        );
      },
      branches: [
        StatefulShellBranch(
          routes: [
            GoRoute(
              path: '/home',
              builder: (context, state) => HomeScreen(),
            ),
            GoRoute(
              path: '/details',
              builder: (context, state) => DetailsScreen(),
            ),
          ],
        ),
        StatefulShellBranch(
          routes: [
            GoRoute(
              path: '/profile',
              builder: (context, state) => ProfileScreen(),
            ),
            GoRoute(
              path: '/settings',
              builder: (context, state) => SettingsScreen(),
            ),
          ],
        ),
        // Define more branches as needed
      ],
    ),
    // Define additional top-level routes
  ],
);
```

In this code example, StatefulShellRoute defines the overall structure for different branches, and StatefulShellBranch allows the creation of isolated navigation stacks for specific app sections. The result is a clear and organized routing hierarchy, facilitating stateful nested navigation.


Building a Responsive UI for flutter bottom navigation bar with routes

Creating a responsive user interface is crucial to ensure your Flutter app looks and functions well across various device sizes. When implementing stateful nested navigation, responsiveness becomes even more critical to optimize the user experience.


The Importance of Responsive Design

  • Cross-Platform Compatibility: Flutter allows apps to run on multiple platforms, including mobile, web, and desktop. A responsive design ensures that your app adapts to different screen sizes seamlessly.

  • Utilizing LayoutBuilder for Dynamic UI Decisions: Flutter provides the LayoutBuilder widget, which is a powerful tool for making dynamic UI decisions based on the available screen space. It allows your app to respond intelligently to different device sizes.

  • ScaffoldWithNavigationBar (Mobile) and Bottom Navigation Bar: For mobile screens, maintaining a bottom navigation bar is a common practice. The ScaffoldWithNavigationBar widget is designed for this purpose. It integrates with Flutter's NavigationBar to offer a familiar navigation experience on smaller screens.

  • ScaffoldWithNavigationRail (Wider Screens) and NavigationRail for Larger Screens: On wider screens, such as tablets or desktops, the bottom navigation bar might not be the best choice. Instead, Flutter's NavigationRail can be employed. The ScaffoldWithNavigationRail widget is tailored for this scenario, offering a navigation rail on the side for improved usability.

Here's an example of how LayoutBuilder can be used to choose between different UI layouts for mobile and wider screens:

```dart
return LayoutBuilder(
  builder: (context, constraints) {
    // Adjust the UI based on screen width
    if (constraints.maxWidth < 600) {
      // Display mobile-friendly UI with bottom navigation
      return ScaffoldWithNavigationBar(...);
    } else {
      // Utilize a navigation rail for wider screens
      return ScaffoldWithNavigationRail(...);
    }
  },
);
```

By implementing responsive design principles and leveraging LayoutBuilder, you can ensure that your app's user interface remains user-friendly and visually appealing on a variety of devices, accommodating both stateful nested navigation and responsive layout adjustments.


Creating Scaffold Widgets

When implementing stateful nested navigation in Flutter, it's essential to have specialized Scaffold widgets that cater to different screen sizes. Let's delve into the details of two crucial widgets: ScaffoldWithNavigationBar and ScaffoldWithNavigationRail.


ScaffoldWithNavigationBar code example

  • Structure and Purpose: The ScaffoldWithNavigationBar widget is designed for mobile screens, where a traditional bottom navigation bar is a common navigation pattern. It encapsulates the following components:

  • body: This is where the primary content of the app is displayed. It typically hosts the navigation stack controlled by the StatefulNavigationShell.

  • bottomNavigationBar: Integrated with Flutter's NavigationBar, this component provides users with easy access to different sections of the app. It maintains the selected tab and responds to user interactions.

Example Code for ScaffoldWithNavigationBar:

```dart
return ScaffoldWithNavigationBar(
  body: navigationShell,
  selectedIndex: navigationShell.currentIndex,
  onDestinationSelected: _goBranch,
);
```


ScaffoldWithNavigationRail code example

  • Structure and Purpose: The ScaffoldWithNavigationRail widget caters to wider screens, such as tablets or desktops. In these scenarios, a bottom navigation bar might not be the optimal choice, and a side navigation rail offers better usability. Key elements of this widget include:

  • body: Similar to its mobile counterpart, this is where the primary content resides. It accommodates the navigation stack managed by the StatefulNavigationShell.

  • NavigationRail: Positioned on the left side of the screen, this component provides navigation options. It supports labels, icons, and user interactions for seamless navigation.

Example Code for ScaffoldWithNavigationRail

```dart
return ScaffoldWithNavigationRail(
  body: navigationShell,
  selectedIndex: navigationShell.currentIndex,
  onDestinationSelected: _goBranch,
);
```

By employing these specialized Scaffold widgets, you can ensure that your app's navigation adapts smoothly to different screen sizes, enhancing user experience and usability. Whether on mobile or wider screens, users will have intuitive access to the app's various sections while enjoying a responsive and visually appealing interface.


Implementing Stateful Nested Navigation

Now, let's dive into the implementation details of stateful nested navigation using Flutter's GoRouter package. This section will guide you through integrating stateful nested navigation into your app's user interface, explain the LayoutBuilder's role in selecting the appropriate widget, and provide code snippets to demonstrate how to implement stateful navigation using GoRouter. Additionally, we'll address potential issues and their solutions.


Integrating Stateful Nested Navigation into the UI:

  • To implement stateful nested navigation, we need to create a hierarchical navigation structure. Each branch of this structure can maintain its own navigation stack, preserving the navigation state independently.

  • The root of this structure can be defined using the StatefulShellRoute, which is responsible for displaying a UI shell with separate Navigators for its sub-routes.

  • StatefulShellBranch instances represent individual branches, each with its own Navigator. These branches are like parallel navigation trees within your app, allowing you to manage and navigate them independently.


Using LayoutBuilder for Responsive UI

  • A crucial aspect of stateful nested navigation is providing a responsive user interface. Depending on the screen size, we may want to use different navigation patterns.

  • The LayoutBuilder widget helps us make dynamic UI decisions. It provides information about the constraints of its parent widget, allowing us to adapt our UI accordingly.

  • By using LayoutBuilder, we can choose between two specialized Scaffold widgets: ScaffoldWithNavigationBar for mobile screens and ScaffoldWithNavigationRail for wider screens.

Below are code snippets illustrating the implementation of stateful nested navigation with GoRouter

```dart
final _rootNavigatorKey = GlobalKey<NavigatorState>();
final _shellNavigatorAKey = GlobalKey<NavigatorState>(debugLabel: 'shellA');
final _shellNavigatorBKey = GlobalKey<NavigatorState>(debugLabel: 'shellB');
final goRouter = GoRouter(
  initialLocation: '/a',
  navigatorKey: _rootNavigatorKey,
  routes: [
    StatefulShellRoute.indexedStack(
      builder: (context, state, navigationShell) {
        return LayoutBuilder(builder: (context, constraints) {
          if (constraints.maxWidth < 450) {
            return ScaffoldWithNavigationBar(
              body: navigationShell,
              selectedIndex: navigationShell.currentIndex,
              onDestinationSelected: _goBranch,
            );
          } else {
            return ScaffoldWithNavigationRail(
              body: navigationShell,
              selectedIndex: navigationShell.currentIndex,
              onDestinationSelected: _goBranch,
            );
          }
        });
      },
      branches: [
        StatefulShellBranch(
          navigatorKey: _shellNavigatorAKey,
          routes: [
            GoRoute(
              path: '/a',
              pageBuilder: (context, state) => const NoTransitionPage(
                child: RootScreen(label: 'A', detailsPath: '/a/details'),
              ),
              routes: [
                GoRoute(
                  path: 'details',
                  builder: (context, state) =>
                      const DetailsScreen(label: 'A'),
                ),
              ],
            ),
          ],
        ),
        // Define more branches as needed...
      ],
    ),
  ],
);
```


Addressing Potential Issues

  • One common issue is creating a GoRouter instance inside a widget's build method. To prevent rebuilding the router unnecessarily, declare it as a global variable or within a provider.

  • Be mindful of the LayoutBuilder's layout breakpoint, which determines when to switch between mobile and wider screen UI. Adjust this breakpoint according to your design requirements.

  • By following these steps and code examples, you can successfully implement stateful nested navigation in your Flutter app, providing users with a seamless and responsive navigation experience.


How to navigate programmatically between different branches?

Now that you have a solid understanding of stateful nested navigation using Flutter's GoRouter package, it's essential to know how to navigate programmatically between different branches. In this section, we will delve into the details of programmatically navigating between branches, provide examples of how to achieve this, and clarify where this method can be applied within the route hierarchy.


Programmatically Navigating Between Branches

  • Programmatically navigating between branches involves switching the active branch of your stateful nested navigation system based on certain triggers or user actions. This can be beneficial when you want to guide users to specific sections of your app.

  • To accomplish this, you can use the `StatefulNavigationShell.of(context).goBranch(index)` method. This method allows you to specify the index of the branch you want to navigate to.

Here's an example of how to programmatically navigate to a different branch from within a sub-route

```dart
// Inside a sub-route widget
StatefulNavigationShell.of(context).goBranch(1); // Navigates to the second branch
```


Where to Apply Programmatically Navigation?

  • Programmatically navigating to different branches is typically useful when you want to respond to user actions or certain conditions. For example, in a bottom navigation bar scenario, you might programmatically switch to a different tab (branch) when the user taps a specific icon or button.

  • You can apply this method within the route hierarchy of each branch. As shown in the example, you can call `StatefulNavigationShell.of(context).goBranch(index)` from any sub-route of the StatefulShellRoute.

By understanding how to navigate programmatically between branches, you gain more control over the user's navigation experience within your Flutter app. This capability allows you to create dynamic and context-aware navigation patterns, enhancing the overall usability of your application.


Challenges and Solutions in flutter bottom navigation bar with routes

As you dive into the world of stateful nested navigation with GoRouter, it's essential to be aware of potential challenges and the solutions that can help you overcome them.


Potential Challenges with GoRouter

  • While GoRouter is a powerful and flexible routing package, it's not without its quirks. Users may encounter issues such as widget key conflicts or unexpected behavior, particularly when hot reloading.

  • Managing the complexity of nested navigation, especially in large applications, can be challenging without proper structuring and documentation.


Importance of Proper Package Usage and Contribution

  • To mitigate these challenges, it's crucial to follow best practices when using GoRouter. Declare your GoRouter instance as a global variable to prevent it from being rebuilt during hot reloads, which can lead to key conflicts.

  • Contribute to the GoRouter community by reporting issues and providing feedback. The GoRouter team and contributors actively work to address bugs and improve the package.


Continuous Improvement of GoRouter

  • GoRouter is an actively maintained package, and improvements are continuously made. New features and enhancements are regularly introduced to make routing in Flutter smoother and more efficient.

  • Staying up-to-date with the latest version of GoRouter and its documentation ensures you benefit from the latest improvements and fixes.

By understanding these challenges and adopting best practices, you can make the most of GoRouter's capabilities while minimizing potential roadblocks in your Flutter app development journey. Remember that the Flutter community is supportive, and your contributions can help improve GoRouter for everyone.


Conclusion for flutter bottom navigation bar with routes

In conclusion, this article has provided a comprehensive guide to implementing stateful nested navigation with Flutter using the GoRouter package. Let's recap the key takeaways and benefits of this approach:


Key Takeaways:

  • Stateful nested navigation enhances the user experience by allowing seamless switching between tabs or branches in your app.

  • GoRouter is a powerful routing package for Flutter that supports stateful nested navigation.

  • Responsive UI design ensures that your app adapts to different screen sizes, providing an optimal user experience.


Benefits of Stateful Nested Navigation and Responsive UI

  • Stateful nested navigation allows you to manage complex app structures efficiently and preserves the navigation state within each tab or branch.

  • Responsive UI design ensures that your app looks and behaves well on various devices, from mobile phones to larger screens.


Encouragement for Flutter Developers

  • We encourage Flutter developers to explore GoRouter and leverage its capabilities for stateful nested navigation in their projects.

  • By adopting the techniques discussed in this article, you can create Flutter apps that provide a seamless and intuitive user experience.

As you embark on your Flutter app development journey, remember that stateful nested navigation and responsive design are essential components of modern mobile applications. With GoRouter and the insights shared in this article, you have the tools and knowledge to build exceptional Flutter apps that delight your users across various devices. Happy coding!