# The Ultimate Guide to GoRouter: Navigation in Flutter Apps Part -1 (Go Router Setup, Declarative Routing, Type Safety , Path and Query Params)

As a Flutter developer, you're likely no stranger to the importance of navigation in your app. Whether you're building a simple to-do list or a complex e-commerce platform, navigation is a crucial aspect of providing a seamless user experience.

In this article, we'll dive into the world of GoRouter, a powerful navigation library for Flutter that makes it easy to manage your app's navigation flow.

**What is GoRouter?**

GoRouter is an open-source navigation library for Flutter that provides a simple and efficient way to manage your app's navigation. Developed by the Flutter team, GoRouter is designed to simplify the process of navigating between screens, handling route changes, and managing the app's navigation stack.

**Why Use GoRouter? All the Features comprehensively: We are going to cover every single one in this article.**

1. **Declarative Routing:**
    
    * GoRouter allows you to define routes in a declarative manner, which means all routing logic is centralized and not scattered throughout the application. This makes it easier to understand the navigation flow and modify it.
        
2. **Built-in Deep Linking Support:**
    
    * Handling deep links is more straightforward with GoRouter, as it seamlessly integrates deep linking into its navigation system. This simplifies the process of linking to specific screens from external sources.
        
3. **Type Safety:**
    
    * GoRouter supports strong typing for route parameters, reducing runtime errors and enhancing code reliability. This strong typing ensures that parameters required by different screens are correctly passed and interpreted.
        
4. **Simplified Handling of Nested Routes:**
    
    * Managing nested routes (routes within routes) is much easier with GoRouter. This is particularly useful in complex applications with multiple layers of navigation, such as tabs within stacks.
        
5. **Redirection and Guards:**
    
    * GoRouter provides built-in support for redirection and guards. Guards are useful for implementing requirements like user authentication before accessing certain parts of an app. Redirection helps in controlling the navigation flow based on specific conditions (e.g., redirecting to a login screen if the user is not authenticated).
        
6. **Error Handling:**
    
    * It offers robust error handling capabilities, allowing developers to manage unknown routes or errors within the routing system effectively.
        
7. **Programmatic and Declarative Navigation:**
    
    * While GoRouter is primarily declarative, it also supports programmatic navigation where necessary, giving developers the best of both worlds.
        
8. **Performance:**
    
    * With its efficient management of route states and changes, GoRouter can lead to better performance in complex applications, where managing the navigation stack can otherwise become.
        

### Starter Code:

This is the starter code of the app: [https://github.com/khkred/flutter\_comp\_go\_router/tree/starter-code](https://github.com/khkred/flutter_comp_go_router/tree/starter-code)

This is the current tree of the screens inside the starter code

```bash
lib/screens/
├── dashboard_page.dart
├── deep_link_page.dart
├── details_page.dart
├── error_page.dart
├── home_page.dart
├── login_page.dart
├── profile_page.dart
└── settings_page.dart

1 directory, 8 files
```

Each screen will have a specific purpose in the future:

1. **HomePage** - Declarative Routing
    
2. **Details Page** - Type Safety and Parameters
    
3. **Settings Page** - Nested Routes
    
4. **Profile Page** - Type Safety and Parameters
    
5. **Login Page** - Redirection and Guards
    
6. **Dashboard Page** - Nested Routes
    
7. **Error Page** - Error Handling
    
8. **Deep Link Page** - Built-in Deep Linking Support
    

## Setup Declarative Routing:

First we have to setup the Go Router in our app. Our GoRouter package is already added into the Starter Code. But If you want to add it into your pubspec.yaml. Run the following command in your terminal:

```bash
flutter pub add go_router
```

Now that the GoRouter is added. Let's setup the initial navigation for GoRouter in a separate file named `app_router.dart` . Here is the code:

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

class AppRouter {
  static final GoRouter router = GoRouter(
    routes: [
      GoRoute(
        path: '/',
        builder: (BuildContext context, GoRouterState state) => const HomePage(),
      ),
    ],
  );
}
```

### **Explanation:**

* **GoRouter Initialization**: We create a `static final` instance of `GoRouter` named `router`. This instance is initialized with a list of routes.
    
* **Route Definition**: <mark>The </mark> `GoRoute` <mark> constructor is used to define a route</mark>.
    
    * `path`: This specifies the path in the URL. Here, `'/'` denotes the root or home route.
        
    * `builder`: This function returns the widget that should be displayed when navigating to this route. In this case, it returns an instance of `HomePage`.
        

### **Using**`AppRouter` in Your Main Application:

To use the `AppRouter` in your main application, modify your `main.dart` file to utilize the GoRouter instance for routing:

```dart
import 'package:flutter/material.dart';
import 'app_router.dart';
void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: 'Go Router Example',
      routerConfig: AppRouter.router,
    );
  }
}
```

Perfect. So now when we open the app it directly goes to the homepage:

![Declarative Routing](https://media0.giphy.com/media/v1.Y2lkPTc5MGI3NjExYmkyZmNxNWJ6d3hqOGV0M2FiOGowdGlwanQ1bDJ1Mmo0dm9kcjEwciZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/AOY2wEfP3C9raElUKI/giphy.gif align="center")

So this covers Declarative Routing. You can find the total code here: [https://github.com/khkred/flutter\_comp\_go\_router/tree/declarative-routing](https://github.com/khkred/flutter_comp_go_router/tree/declarative-routing)

# Type Safety and Parameters. Part -1 (Path Parameters)

## Example 1: `DetailsPage`

Let's set up the **Details Page** to demonstrate type safety and parameter handling using GoRouter. We'll create a route that requires a parameter, such as an ID, to fetch and display specific details on the **Details Page**.

### **Step 1: Update the**`DetailsPage` Widget

First, let’s modify the `DetailsPage` to accept a parameter, say an item ID, which it will use to display specific content.

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

class DetailsPage extends StatelessWidget {
  final int itemId;

  // Ensuring type safety with required parameters
  const DetailsPage({super.key, required this.itemId});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Details Page')),
      body: Center(
        child: Text('Displaying details for item ID: $itemId',
            style: const TextStyle(fontSize: 24)),
      ),
    );
  }
}
```

### **Step 2: Add the Route in**`AppRouter`

Now, integrate this page into your `AppRouter` by defining a route that includes the parameter. You’ll capture the parameter from the URL and pass it to the `DetailsPage`.

Update your `app_router.dart` file like this:

```dart
import 'package:flutter/material.dart';
import 'package:flutter_comp_go_router/screens/details_page.dart';
import 'package:go_router/go_router.dart';
import 'screens/home_page.dart';

class AppRouter {
  static final GoRouter router = GoRouter(
    routes: [
      GoRoute(
        path: '/',
        builder: (BuildContext context, GoRouterState state) =>
            const HomePage(),
      ),
      
      GoRoute(
          path: 'details/:itemId',
          builder: (BuildContext context, GoRouterState state) {
            final params = state.pathParameters['itemId'];
            final int itemId = int.tryParse(params ?? '') ?? 0;
            return DetailsPage(itemId: itemId);
          }),
    ],
  );
}
```

Now we are adding a textfield in the `home_page.dart` where a user can enter a number and the app will take you the `details_page.dart` with that id.

Here's the relevant function in home\_page.dart with navigation:

```dart
  void _navigateToDetails() {
    if (_detailsPageIdController.text.isNotEmpty) {
      final itemId = int.tryParse(_detailsPageIdController.text);
      if (itemId != null) {
        //We are going to the details page with the item ID
        context.go('/details/$itemId');
      } else {
        // Error handling for non-numeric input
        showDialog(
          context: context,
          builder: (context) => AlertDialog(
            title: const Text('Error'),
            content: const Text('Please enter a valid number.'),
            actions: <Widget>[
              TextButton(
                onPressed: () => Navigator.of(context).pop(),
                child: const Text('OK'),
              ),
            ],
          ),
        );
      }
    }
  }
```

![Type Safety and Params Details Page](https://media3.giphy.com/media/v1.Y2lkPTc5MGI3NjExOXlkYnA4dXQzcHlscHgzbnEwdG1kcXU0MnY1bGdsdWt4cXh6bG81NCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/iB5vwkWb61GNylABJa/giphy.gif align="center")

But you cannot go back from the details page. If you want to go back then all you have to do is change

```dart
 context.go('/details/$itemId');
```

to

```dart
 context.push('/details/$itemId');
```

![Go back to Homepage from Details Page](https://media3.giphy.com/media/v1.Y2lkPTc5MGI3NjExazR2NndiZ2VjeXZtd2V0eHh1cnc5cDFnamVkNTgwdWMzY3BjMGg3ZSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/D6oqpFGcFaubnDtky8/giphy.gif align="center")

## Example 2: `ProfilePage` Accessing from URL

The `ProfilePage` will demonstrate how to securely pass and handle route parameters, specifically focusing on ensuring type safety, which is crucial for preventing runtime errors and bugs related to data types.

### **Step 1: Update the ProfilePage Widget**

Let's define the `ProfilePage` to accept a user ID as a parameter and display it. This is a basic example of type safety, as the page will expect an integer and handle it appropriately. This is extremely similar to `DetailsPage` but we want to see how we'll access it from the URL

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

class ProfilePage extends StatelessWidget {
  final int userId;

  // Ensuring type safety with required integer parameter
  const ProfilePage({super.key, required this.userId});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Profile Page')),
      body: Center(
        child: Text('Profile for user ID: $userId'),
      ),
    );
  }
}
```

### **Step 2: Configure the Route in AppRouter**

Now, integrate the `ProfilePage` into your routing setup in the `AppRouter`. This involves capturing a path parameter from the URL and passing it securely to the `ProfilePage`.

So let's go ahead and add the following Route to our routes in `app_router.dart`

```dart
GoRoute(
path: '/profile/:userId',
builder: (BuildContext context, GoRouterState state){
final params = state.pathParameters['userId'];
// Here we are making sure that the userId is an integer for Type Safety
final int userId = int.tryParse(params ?? '') ?? 0;
return ProfilePage(userId: userId);
      }),
```

In this setup:

* **Path Parameter Handling:** The route uses `:userId` as a path parameter. This parameter is extracted from the URL and converted to an integer (`int.tryParse`). <mark>This ensures that the ID passed to </mark> `ProfilePage` <mark> is always an integer, maintaining type safety.</mark>
    
* **Error Handling:**<mark>The fallback to </mark> `0` <mark> if parsing fails is a simple error handling mechanism.</mark>
    

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

We want to test the app using URLS. So First let's add web support for our app. Since our current app only supports Android and iOS. Run the following command in our terminal

1. Add Web Platform to our existing Flutter Project
    

```bash
flutter create --platforms web .
```

2. Enable Web Support. Just to be safe restart your editor
    

```bash
flutter config --enable-web
```

3. Run the project in chrome
    

```bash
flutter run -d chrome
```

Now after we run the app on Chrome. We can enter the URL

![Profile Page Gif](https://media0.giphy.com/media/v1.Y2lkPTc5MGI3NjExMDAyZ2JsbzJpajJ0dGJsc2l0Zm90bWRmMzYyZDgwNXo4ZGU4YTU0NyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/a9gsSOuQvNYCn4GNz3/giphy.gif align="center")

**Remove the # from URL:**

If you wish to eliminate the ‘#’ symbol from your URL, you can use the `usePathUrlStrategy` class in Flutter.

So in our `main.dart` we have to add the following code

```dart
import 'package:flutter_web_plugins/flutter_web_plugins.dart';

void main(){
  usePathUrlStrategy();
  runApp(MyApp());
}
```

You can find the final source code here: [https://github.com/khkred/flutter\_comp\_go\_router/tree/type\_safety\_and\_parameters](https://github.com/khkred/flutter_comp_go_router/tree/type_safety_and_parameters)

## Type Safety and Parameters. Part -2 (Query Parameters)

**Definition**: Query parameters are used to provide optional modifications or filter data on a given resource or page. They are not part of the URL path but are appended at the end after a `?` mark.

Example:

```dart
GoRoute(
  path: '/users',
  builder: (context, state) => UsersScreen(filter: state.queryParameters['filter']),
),
```

Difference between Path Parameters and Query Parameters:  
Here is a table describing the difference between the two:

|  | Path Parameter | Query String Parameter |
| --- | --- | --- |
| **Syntax** | `:userId` | `?filter=admins` |
| **Access** | `state.pathParameters['userId']` | `state.uri.queryParameters['filter']` |
| **Example** | `/users/:userId` | `/users?filter=admins` |
| **Purpose** | Specify a dynamic part of the URL path | Specify a dynamic query string parameter |
| **Usage** | Use in path to specify a dynamic value | Use in query string to specify a dynamic value |

In summary, path parameters are used to specify dynamic parts of the URL path, while query string parameters are used to specify dynamic values in the query string.

### **Step 1: Define the Screen That Will Use Query Parameters**

First, let's define a Flutter screen that can accept query parameters. For example, we might create a `UsersScreen` that can filter users based on a query parameter named `filter`. Here’s a basic implementation:

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

class UsersScreen extends StatelessWidget {
  final String? filter;

  const UsersScreen({super.key, this.filter});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Users Screen')),
      body: Center(
        // Displaying the filter if it exists
        child: Text(filter != null ? 'Filtering by: $filter' : 'No filter applied'),
      ),
    );
  }
}
```

### **Step 2: Setup the Route with Query Parameters in**`AppRouter`

Next, let's add a route to our `AppRouter` to handle a route that includes query parameters. Here’s how you might set it up:

```dart
// Pay Attention to this route as it has a query parameter and the filter is passed to the UsersScreen
GoRoute(path: '/users',
builder: (BuildContext context, GoRouterState state){
final String? filter = state.uri.queryParameters['filter']; 
return UsersScreen(filter: filter);
      }),
```

So this functions in the HomePage helps us navigate to the `UsersPage()`

```dart
void _navigateToUsers() {
    final filter = _filterController.text;
    if (filter.isNotEmpty) {
      context.push('/users?filter=$filter');
    } else {
      context.push('/users');
    }
  }
```

![Query Parameters](https://media4.giphy.com/media/v1.Y2lkPTc5MGI3NjExa2x5MGdybDMxc2xwZTN0Y3g3N2N0eXgzYmphaTFmcWh3d3ptdXJ0ayZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/mO1vjER2iYi34fgvsy/giphy.gif align="center")

Here's the current source code: [https://github.com/khkred/flutter\_comp\_go\_router/tree/query\_parameters](https://github.com/khkred/flutter_comp_go_router/tree/query_parameters)

## Bonus Sections

### Bonus 1: Setting an Initial Location

If we want to set an initial location for the app regardless of the root location we can do it by adding the `initiaLocation:`

First let's add a `sample_page.dart` to our app:

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample Page')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('This is the sample page, we got from initial Location'),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                context.go('/'); // This will navigate to the home page
              },
              child: const Text('Go to Home Page'),
            ),
          ],
        ),
      ),
    );
  }
}
```

Now this is our updated `route`r:

```dart

class AppRouter {
  static final GoRouter router = GoRouter(
    initialLocation: '/sample',
    routes: [
      GoRoute(
        path: '/',
        builder: (BuildContext context, GoRouterState state) =>
            const HomePage(),
      ),
      GoRoute(path: '/sample',
        builder: (BuildContext context, GoRouterState state) => const SamplePage(),),
.........
```

This is what I get when I run the app right now:

![initial location](https://media2.giphy.com/media/v1.Y2lkPTc5MGI3NjExOXEwb3B2a2IwMWowdjE3eXI2ZWRqNmI3OGJraWdvd3Mxcm85bjBraCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/CROQAe64W1IsD7xQPi/giphy.gif align="center")

As always you can find the link for the bonus code here: [https://github.com/khkred/flutter\_comp\_go\_router/tree/type\_safety\_and\_parameters](https://github.com/khkred/flutter_comp_go_router/tree/type_safety_and_parameters)

### Bonus -2: Named Routes

You can set a name for any route. So that we can call it instead of calling the entire path.

For instance let's give a name to our `HomePage()` route.

```dart
 GoRoute(
        name: 'home',
        path: '/',
        builder: (BuildContext context, GoRouterState state) =>
            const HomePage(),
      ),
```

Now I can navigate from our `SamplePage()` like this:

```dart
ElevatedButton(
              onPressed: () {
                context.goNamed('home'); // This will navigate to the home page
              },
              child: const Text('Go to Home Page'),
            ),
```

You can find the code here: [named route commit](https://github.com/khkred/flutter_comp_go_router/commit/1e288ab00971909573213bd15b477a9113316601)

### Bonus 3: Named Routes with Path Parameters

For instance let's set a name for our details route:

```dart
GoRoute(
        name: 'details',
        path: '/details/:itemId',
      ...),
```

Now if we want to call it via name. Let's modify the `_navigateToDetails()` in our `home_page`

```dart
//Old Navigation
 //context.push('/details/$itemId');

//New Navigation with named
        context.pushNamed(
          'details',
          pathParameters: {'itemId': '$itemId'},
        );
      }
```

Here's the total code: [named route with path parameters commit](https://github.com/khkred/flutter_comp_go_router/commit/23a62f49f53b2635c75c5e40dccebcba2923d957)

### Bonus 4: Named Routes with Query Parameters

Let's add a name to our `UsersScreen()` route too:

```dart
GoRoute(
          path: '/users',
          name: 'users',
...),
```

Now we can update the `_navigateToUsers()` in our `HomePage` like this:

```dart
//Old Query Parameter
//context.push('/users?filter=$filter');

//New named query parameters
      context.pushNamed(
        'users',
        queryParameters: {'filter': filter},
      );
```

Here's the code: [Named Route with Query Parameters Commit](https://github.com/khkred/flutter_comp_go_router/commit/4841cce5a8bb7563b1a27d4574e6919c506e5c85)

### Remaining Parts:

Here's the link for part 2: [https://harishkunchala.com/the-ultimate-guide-to-gorouter-navigation-in-flutter-apps-part-2-nested-routers-redirect-guard-error-handling](https://harishkunchala.com/the-ultimate-guide-to-gorouter-navigation-in-flutter-apps-part-2-nested-routers-redirect-guard-error-handling)

Here's the link for part 3: [https://harishkunchala.com/the-ultimate-guide-to-gorouter-navigation-in-flutter-apps-part-3-custom-transitions](https://harishkunchala.com/the-ultimate-guide-to-gorouter-navigation-in-flutter-apps-part-3-custom-transitions)
