FlutterRouter GeneratorApr 29, 20264 min read191

From Chaotic Routes to Flawless Flows: Type-Safe Flutter Navigation

If you’ve been building Flutter apps for a while, you know that navigation can quickly become a tangled mess of string-based routes and unhandled arguments. Enter go_router—the declarative routing package maintained by the Flutter team. It’s fantastic, but what if we could make it even better?

By pairing go_router with go_router_builder*, we can unlock the power of code generation to achieve 100% *type-safe navigation. No more typos in route names, and no more guessing what arguments a screen requires.

1_1vS_MTWDuKUdD4KAvejDUQ.webp

Let’s dive into how to set this up!

Why Use go_router_builder?
----------------------------

While standard go_router relies on strings (e.g., context.go('/details/42')), go_router_builder generates strongly-typed route objects.

The benefits are huge:

  • Compile-time safety: If you misspell a route or forget a required parameter, your app won’t compile.
  • IntelliSense support: Your IDE knows exactly what routes exist and what arguments they need.
  • Cleaner code: You define your routes as Dart classes, keeping your navigation logic organized and predictable.
  • Step 1: Add Dependencies
    ------------------------

    First, let’s add the necessary packages to your pubspec.yaml file. You'll need the router package itself, along with the build tools for code generation.

    Run these commands in your terminal:

    flutter pub add go_router
    flutter pub add -d build_runner go_router_builder
    

    Step 2: Define Your Screens
    ---------------------------

    Let’s imagine a simple app with two screens: a HomeScreen and a UserDetailScreen that requires a user ID.

    // home_screen.dart
    import 'package:flutter/material.dart';
    class HomeScreen extends StatelessWidget {
      const HomeScreen({super.key});
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: const Text('Home')),
          body: Center(
            // We will add navigation here shortly!
            child: ElevatedButton(
              onPressed: () {}, 
              child: const Text('Go to User Details'),
            ),
          ),
        );
      }
    }
    

    Step 3: Define Your Routes
    --------------------------

    Now, instead of a giant router configuration block, we define our routes using classes that extend GoRouteData and annotate them with @TypedGoRoute.

    Create a file named app_routes.dart:

    // app_routes.dart
    import 'package:flutter/material.dart';
    import 'package:go_router/go_router.dart';
    import 'home_screen.dart';
    import 'user_detail_screen.dart';
    // This is the file that build_runner will generate
    part 'app_routes.g.dart';
    // 1. Define the Home Route
    @TypedGoRoute<HomeRoute>(
      path: '/',
      routes: [    // 2. Define the User Detail Route as a nested route
        TypedGoRoute<UserDetailRoute>(
          path: 'user/:userId',
        ),
      ],
    )
    class HomeRoute extends GoRouteData {
      const HomeRoute();
      @override
      Widget build(BuildContext context, GoRouterState state) 
        => const HomeScreen();
    }
    class UserDetailRoute extends GoRouteData {
      final String userId;
      // The builder will automatically extract 'userId' from the path
      const UserDetailRoute({required this.userId});
      @override
      Widget build(BuildContext context, GoRouterState state) {
        return UserDetailScreen(userId: userId);
      }
    }
    

    Step 4: Run the Code Generator
    ------------------------------

    Because we are using part 'app_routes.g.dart';, Dart will show an error until we generate that file. Open your terminal and run:

    Bash

    flutter pub run build_runner build --delete-conflicting-outputs
    

    _(Tip: Use_ _watch_ _instead of_ _build_ _if you want the generator to run automatically as you make changes)._

    Once this finishes, app_routes.g.dart will be created, and your errors will disappear!

    Step 5: Initialize the Router
    -----------------------------

    Now, set up MaterialApp.router in your main.dart using the automatically generated $appRoutes list.

    // main.dart
    import 'package:flutter/material.dart';
    import 'package:go_router/go_router.dart';
    import 'app_routes.dart';
    void main() {
      runApp(const MyApp());
    }
    final GoRouter _router = GoRouter(
      // The generated list of routes from app_routes.g.dart
      routes: $appRoutes, 
      initialLocation: '/',
    );
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
      @override
      Widget build(BuildContext context) {
        return MaterialApp.router(
          title: 'Go Router Builder Demo',
          routerConfig: _router,
        );
      }
    }
    

    Step 6: Navigate with Type Safety!
    ----------------------------------

    Now for the best part. Instead of calling context.go('/user/123'), we can use the generated route class directly. Head back to your HomeScreen and update the button's onPressed callback:

    // Inside HomeScreen's build method:
    ElevatedButton(
      onPressed: () {
        // Look at this beautifully typed navigation!
        const UserDetailRoute(userId: '123').go(context);
      },
      child: const Text('Go to User Details'),
    )
    

    If you ever change userId to an integer in your route definition, the compiler will immediately flag this button as an error, saving you from a runtime crash!

    Wrapping Up
    -----------

    By adopting go_router_builder, you bridge the gap between Flutter's powerful declarative routing and Dart's strict type safety. It takes a few extra minutes to set up, but the peace of mind you get when scaling your app is well worth the effort.

    Happy coding!

    Enjoyed this read?

    1likes
    Comments

    Loading…