# The Ultimate Guide to GoRouter: Navigation in Flutter Apps Part -2 (Nested Routers, Redirect, Guard, Error Handling)

You can find the Part 1 here: [https://tinyurl.com/k3f64hwv](https://tinyurl.com/k3f64hwv)

# **Dashboard Page - Nested Routes**

## Example 1: Tabbed Content with Widgets

Nested routes are useful for organizing complex user interfaces with multiple layers of navigation, such as a dashboard with multiple sections, each represented as a tab or a sub-page. This approach helps maintain a clean and manageable structure in your routing setup.

### **Step 1: Define the DashboardPage Widget**

Let's create our `DashboardPage` with three tabs: Home, Settings, and Profile. Each tab will represent a nested route.

```dart
import 'package:flutter/material.dart';

class DashboardPage extends StatelessWidget {
  const DashboardPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 3,
      child: Scaffold(
        appBar: AppBar(
          title: const Text('Dashboard'),
          bottom: const TabBar(
            tabs: [
              Tab(icon: Icon(Icons.home), text: 'Home'),
              Tab(icon: Icon(Icons.settings), text: 'Settings'),
              Tab(icon: Icon(Icons.account_circle), text: 'Profile'),
            ],
          ),
        ),
        body: const TabBarView(
          children: [
            Center(child: Text('Home Tab Content')),
            Center(child: Text('Settings Tab Content')),
            Center(child: Text('Profile Tab Content')),
          ],
        ),
      ),
    );
  }
}
```

### **Step 2: Configure Nested Routes in AppRouter**

Now, let's set up the routes in the `AppRouter`. Although the above `DashboardPage` uses `DefaultTabController` for simplicity, nested routes in GoRouter would involve setting up child routes under a parent route.

```dart
GoRoute(
          path: '/dashboard',
          builder: (BuildContext context, GoRouterState state) =>
              const DashboardPage(),
          routes: [
            GoRoute(
              path: 'home',
              builder: (BuildContext context, GoRouterState state) =>
                  const Center(child: Text("Home Tab Content")),
            ),
            GoRoute(
              path: 'settings',
              builder: (BuildContext context, GoRouterState state) =>
                  const Center(child: Text("Settings Tab Content")),
            ),
            GoRoute(
              path: 'profile',
              builder: (BuildContext context, GoRouterState state) =>
                  const Center(child: Text("Profile Tab Content")),
            ),
          ]),
```

### **Explanation**

* **Parent Route (**`/dashboard`): This is the main dashboard page.
    
* **Nested Routes**: Under the dashboard, we have three sub-routes (`home`, `settings`, `profile`). Each of these could potentially be fleshed out with more complex functionality or screens.
    

### **Step 3: Testing the Setup**

To test this setup:

1. Let's create an `ElevatedButton` on HomePage to go to `Dashboard`
    
2. Click on the tabs and observe that the content changes according to the selected tab.
    

![Nested Routes on Phone](https://media0.giphy.com/media/v1.Y2lkPTc5MGI3NjExMWt1NTNiemYwMmhoNno3b3cwcWhpOGQ5aDQ5cGNnOHZncHFldG1kZCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/POsxrgQh7Xb1YxKY7B/giphy.gif align="center")

3. We can also directly navigate to `/dashboard/home`, `/dashboard/settings`, or `/dashboard/profile` to see if the respective content is shown.
    

## Example 2: Tabbed Content with Actual Screens

So let's create an all the screens to be put in the tabs. I am going to go with basic UI but if you want you can additional features inside each screen too.

### **Step 1: Create Specific Screens for Each Tab**

First, let's define separate widgets for each tab to be used in the nested routes:

```dart
import 'package:flutter/material.dart';

// HomeTabScreen with a unique background color
class HomeTabScreen extends StatelessWidget {
  const HomeTabScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.blue[100], // Light blue background
      child: const Center(child: Text('Home Tab Content')),
    );
  }
}

// SettingsTabScreen with a unique background color
class SettingsTabScreen extends StatelessWidget {
  const SettingsTabScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.green[100], // Light green background
      child: const Center(child: Text('Settings Tab Content')),
    );
  }
}

// ProfileTabScreen with a unique background color
class ProfileTabScreen extends StatelessWidget {
  const ProfileTabScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.purple[100], // Light purple background
      child: const Center(child: Text('Profile Tab Content')),
    );
  }
}
```

### **Step 2: Update the**`DashboardPage` to Include Specific Screens

Modify the `DashboardPage` to use these new widgets within the `TabBarView`:

```dart
class DashboardPage extends StatelessWidget {
  const DashboardPage({super.key});

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 3,
      child: Scaffold(
        appBar: AppBar(
          title: const Text("Dashboard"),
          bottom: const TabBar(
            tabs: [
              Tab(icon: Icon(Icons.home), text: "Home"),
              Tab(icon: Icon(Icons.settings), text: "Settings"),
              Tab(icon: Icon(Icons.account_circle), text: "Profile"),
            ],
          ),
        ),
        body: const TabBarView(
          children: [
            HomeTabScreen(),
            SettingsTabScreen(),
            ProfileTabScreen(),
          ],
        ),
      ),
    );
  }
}
```

### **Step 3: Integrate with GoRouter**

Now, integrate these screens into your GoRouter setup. Since we want to use GoRouter to handle the actual switching between tabs based on the route (rather than just using `DefaultTabController`), so let's set up child routes:

```dart
 GoRoute(
          path: '/dashboard',
          builder: (BuildContext context, GoRouterState state) =>
              const DashboardPage(),
          routes: [
            GoRoute(
              path: 'home',
              builder: (BuildContext context, GoRouterState state) =>
                  const HomeTabScreen(),
            ),
            GoRoute(
              path: 'settings',
              builder: (BuildContext context, GoRouterState state) =>
                  const SettingsTabScreen(),
            ),
            GoRoute(
              path: 'profile',
              builder: (BuildContext context, GoRouterState state) =>
                  const ProfileTabScreen(),
            ),
          ])
```

### **Step 4: Testing the Setup**

Run your application:

![Web View of the Dashboard](https://media1.giphy.com/media/v1.Y2lkPTc5MGI3NjExNzY2dTUxY3NicWF4dnJiNXFvaDR5eGp6OGsxZ3BlZnUwa2kxNnl3aSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/swwf0lUyUX7MwEQDvs/giphy.gif align="center")

You can find the entire source code for nested routes here: [https://github.com/khkred/flutter\_comp\_go\_router/tree/nested\_routes](https://github.com/khkred/flutter_comp_go_router/tree/nested_routes)

# Concept: Redirection and Guards

* Guards in GoRouter allow you to perform checks before a route is shown to the user.
    
* If the check fails, you can redirect the user to another route, <mark>typically used for scenarios like redirecting unauthenticated users to a login page.</mark>
    

### **Example Scenario**

Let's implement a simple authentication check where users need to be logged in to view the `DashboardPage`. If they are not authenticated, they will be redirected to a `LoginPage`.

### **Step 1: Define the LoginPage**

First, let’s create a `LoginPage` that provides an option to "log in" for simplicity.

```dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

class LoginPage extends StatelessWidget {
  const LoginPage({super.key});
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Login Page")),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text('Please log in to continue'),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                //Simulate a login by setting a logged in flag
                context.go('/dashboard');
              },
              child: const Text('Log In'),
            ),
          ],
        ),
      ),
    );
  }
}
```

### **Step 2: Setup Authentication Guard**

Let’s define a simple mechanism to check if a user is logged in. For demonstration, we'll use a global variable (Note: In production, you should use a more secure method, such as a state management solution or secure storage, which we'll see in later lessons when we use Riverpod).  
<mark>Since this is a global variable. For now let's declare it in a </mark> `global.dart` <mark> file</mark>

```dart
bool isLoggedIn = false;  // This will be our simple auth check
```

Now let's add a guard to the `AppRouter`:

```dart
 GoRoute(
        path: '/login',
        builder: (BuildContext context, GoRouterState state) =>
            const LoginPage(),
      ),
      
      GoRoute(
          path: '/dashboard',
          builder: (BuildContext context, GoRouterState state) =>
              const DashboardPage(),
          redirect: (BuildContext context, GoRouterState state) {
            if (!isLoggedIn) {
              return '/login'; // Redirect to login if not authenticated
            }
            return null; // No redirection if authenticated
          },
```

Now let's run the code (Remember Right now `isLoggedIn = false` )

![Dashboard when isLogged false](https://media1.giphy.com/media/v1.Y2lkPTc5MGI3NjExeHNtaHRtY2g2emtlaWZrMzY1bzZxNXptdnFteXMwb2JqOXBqbGQxcCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/YIxopWWziYbyjp7un1/giphy.gif align="center")

Now let's set `isLoggedIn = true` and then run the app

![When isLoggedIn true](https://media0.giphy.com/media/v1.Y2lkPTc5MGI3NjExMjM5bnA1NDg1NzE3aWNiaWRzNXoyY2Nvbm83dHRjaGh6b2loOGFwOSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/Ay7Hi31wNZt4ugjSVA/giphy.gif align="center")

You can find the source code for Redirection and Guards here: [https://github.com/khkred/flutter\_comp\_go\_router/tree/redirection\_and\_guards](https://github.com/khkred/flutter_comp_go_router/tree/redirection_and_guards)

# **Concept: Error Handling**

GoRouter <mark>allows us to define a custom error page that can be shown when no matching routes are found</mark> or when an exception occurs during navigation. This is crucial for preventing users from encountering raw error messages and instead provides them with helpful information or actions they can take.

### **Step 1: Define an ErrorPage**

Let's create an `ErrorPage` that will display error information. This page will show a message and provide an option to navigate back to a safe place like the home page.

```dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

class ErrorPage extends StatelessWidget {
  final String? error;

  const ErrorPage({super.key, this.error});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Error')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(error ?? 'Something went wrong!', style: const TextStyle(color: Colors.red, fontSize: 18)),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                context.go('/'); // Navigate back to the home page
              },
              child: const Text('Go Home'),
            ),
          ],
        ),
      ),
    );
  }
}
```

### **Step 2: Integrate Error Handling in AppRouter**

Now, integrate this `ErrorPage` into your `AppRouter` to handle any route errors. You'll set up a global error builder that GoRouter will call whenever navigation to a route fails.

```dart
static final GoRouter router = GoRouter(
    errorBuilder: (BuildContext context, 
GoRouterState state) => ErrorPage(
      error: state.error?.toString(),
    ),
    routes: [...]
);
```

### **Explanation:**

* **Error Builder**: The `errorBuilder` is a callback that gets triggered whenever there is a navigation error. It receives the error state, which includes details about what went wrong.
    
* **Usage of ErrorPage**: The `ErrorPage` is used to display the error. If there's a specific error message available from the navigation attempt, it displays that message; otherwise, it shows a generic "Something went wrong!" message.
    

### **Step 3: Testing the Setup**

To effectively test error handling:

1. **Intentionally Navigate to a Non-Existent Route**: Try navigating to a route that does not exist in your app, such as `/thisdoesnotexist`.
    
2. **Check the Error Page**: Verify that the `ErrorPage` is displayed, and it provides the appropriate error message or a generic message.
    
3. **Use the Button**: Click on the "Go Home" button to ensure it navigates back to the home page correctly.
    

![Error Page](https://media1.giphy.com/media/v1.Y2lkPTc5MGI3NjExNXdydm95d3N6NTU2bjYwMjExdjJxa2loMHlkbjNlaTd0b210dnkwciZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/txkvwLdNhFDIyzJL2l/giphy.gif align="center")

You can find the source code for Error Handling here: [https://github.com/khkred/flutter\_comp\_go\_router/tree/error\_handling](https://github.com/khkred/flutter_comp_go_router/tree/error_handling)

Thanks a lot for reading. Good Luck with your Flutter Journey.
