Question #98MediumFlutter Basics

Flutter go router plugin

#flutter#route

Answer

Overview

GoRouter is the official Flutter routing package from the Flutter team, built on Navigator 2.0. It provides declarative routing, deep linking, and web support with simpler syntax than Navigator 2.0.


Installation

Add to

text
pubspec.yaml
:

yaml
dependencies:
  go_router: ^13.0.0

Basic Setup

dart
import 'package:go_router/go_router.dart';

final router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => HomePage(),
    ),
    GoRoute(
      path: '/profile/:userId',
      builder: (context, state) {
        final userId = state.pathParameters['userId']!;
        return ProfilePage(userId: userId);
      },
    ),
  ],
);

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

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerConfig: router,
    );
  }
}

Navigation Methods

Go (Replace Current Route)

dart
// Navigate to route (replaces current)
context.go('/profile/123');

// With query parameters
context.go('/search?query=flutter&page=2');

Push (Add to Stack)

dart
// Push new route onto stack
context.push('/profile/123');

// Async navigation (wait for result)
final result = await context.push('/edit-profile');
print('Result: $result');

Pop

dart
// Go back
context.pop();

// Pop with result
context.pop('success');

Route Parameters

Path Parameters

dart
GoRoute(
  path: '/user/:userId/post/:postId',
  builder: (context, state) {
    final userId = state.pathParameters['userId']!;
    final postId = state.pathParameters['postId']!;
    return PostPage(userId: userId, postId: postId);
  },
)

// Navigate
context.go('/user/123/post/456');

Query Parameters

dart
GoRoute(
  path: '/search',
  builder: (context, state) {
    final query = state.uri.queryParameters['query'];
    final page = int.tryParse(state.uri.queryParameters['page'] ?? '1');
    return SearchPage(query: query, page: page);
  },
)

// Navigate
context.go('/search?query=flutter&page=2');

Extra Data (Complex Objects)

dart
GoRoute(
  path: '/details',
  builder: (context, state) {
    final user = state.extra as User; // Pass complex object
    return DetailsPage(user: user);
  },
)

// Navigate with extra data
final user = User(id: '123', name: 'Alice');
context.go('/details', extra: user);

Nested Routes (Children)

dart
GoRoute(
  path: '/',
  builder: (context, state) => HomePage(),
  routes: [
    GoRoute(
      path: 'profile',           // Full path: /profile
      builder: (context, state) => ProfilePage(),
    ),
    GoRoute(
      path: 'settings',          // Full path: /settings
      builder: (context, state) => SettingsPage(),
    ),
  ],
)

ShellRoute (Persistent UI)

Keep UI elements (like bottom nav) while navigating between child routes.

dart
final router = GoRouter(
  routes: [
    ShellRoute(
      builder: (context, state, child) {
        return ScaffoldWithNavBar(child: child); // Persistent bottom nav
      },
      routes: [
        GoRoute(
          path: '/',
          builder: (context, state) => HomePage(),
        ),
        GoRoute(
          path: '/profile',
          builder: (context, state) => ProfilePage(),
        ),
        GoRoute(
          path: '/settings',
          builder: (context, state) => SettingsPage(),
        ),
      ],
    ),
  ],
);

class ScaffoldWithNavBar extends StatelessWidget {
  final Widget child;

  const ScaffoldWithNavBar({required this.child});

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: child,
      bottomNavigationBar: BottomNavigationBar(
        onTap: (index) {
          switch (index) {
            case 0: context.go('/');
            case 1: context.go('/profile');
            case 2: context.go('/settings');
          }
        },
        items: [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
          BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
          BottomNavigationBarItem(icon: Icon(Icons.settings), label: 'Settings'),
        ],
      ),
    );
  }
}

Redirects (Guards)

Protect routes with authentication.

dart
final router = GoRouter(
  redirect: (context, state) {
    final isLoggedIn = authService.isLoggedIn;
    final isGoingToLogin = state.matchedLocation == '/login';

    // Not logged in → redirect to login
    if (!isLoggedIn && !isGoingToLogin) {
      return '/login';
    }

    // Logged in + on login page → redirect home
    if (isLoggedIn && isGoingToLogin) {
      return '/';
    }

    return null; // No redirect
  },
  routes: [
    GoRoute(path: '/login', builder: (context, state) => LoginPage()),
    GoRoute(path: '/', builder: (context, state) => HomePage()),
    GoRoute(path: '/profile', builder: (context, state) => ProfilePage()),
  ],
);

Error Handling

Custom 404 page.

dart
final router = GoRouter(
  errorBuilder: (context, state) {
    return ErrorPage(error: state.error);
  },
  routes: [
    GoRoute(path: '/', builder: (context, state) => HomePage()),
  ],
);

class ErrorPage extends StatelessWidget {
  final Exception? error;

  const ErrorPage({this.error});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Error')),
      body: Center(child: Text('Page not found: ${error?.toString()}')),
    );
  }
}

Deep Linking

GoRouter automatically handles deep links.

dart
// Deep link: myapp://product/123
// Navigates to ProductPage with id='123'

GoRoute(
  path: '/product/:id',
  builder: (context, state) {
    final id = state.pathParameters['id']!;
    return ProductPage(productId: id);
  },
)

Android setup (AndroidManifest.xml):

xml
<intent-filter>
  <action android:name="android.intent.action.VIEW" />
  <category android:name="android.intent.category.DEFAULT" />
  <category android:name="android.intent.category.BROWSABLE" />
  <data android:scheme="myapp" />
</intent-filter>

Route Observers

Track navigation for analytics.

dart
class MyObserver extends NavigatorObserver {
  
  void didPush(Route route, Route? previousRoute) {
    print('Pushed: ${route.settings.name}');
  }
}

final router = GoRouter(
  observers: [MyObserver()],
  routes: [...],
);

Named Routes

dart
final router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      name: 'home',
      builder: (context, state) => HomePage(),
    ),
    GoRoute(
      path: '/profile/:userId',
      name: 'profile',
      builder: (context, state) {
        final userId = state.pathParameters['userId']!;
        return ProfilePage(userId: userId);
      },
    ),
  ],
);

// Navigate by name
context.goNamed('profile', pathParameters: {'userId': '123'});

Refresh Listenable (State Changes)

Rebuild routes when state changes (auth status, etc.).

dart
final router = GoRouter(
  refreshListenable: authService, // Listens to ChangeNotifier
  redirect: (context, state) {
    final isLoggedIn = authService.isLoggedIn;
    if (!isLoggedIn) return '/login';
    return null;
  },
  routes: [...],
);

class AuthService extends ChangeNotifier {
  bool _isLoggedIn = false;

  bool get isLoggedIn => _isLoggedIn;

  void login() {
    _isLoggedIn = true;
    notifyListeners(); // Triggers GoRouter refresh
  }
}

Comparison: Navigator 2.0 vs GoRouter

FeatureNavigator 2.0GoRouter
SyntaxImperativeDeclarative ✅
BoilerplateHighLow ✅
Deep linkingManualAutomatic ✅
Web URLsComplexSimple ✅
GuardsManual
text
redirect
Type safetyManualPath params ✅

Full Example

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

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

final router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => HomePage(),
    ),
    GoRoute(
      path: '/profile/:userId',
      builder: (context, state) {
        final userId = state.pathParameters['userId']!;
        return ProfilePage(userId: userId);
      },
    ),
  ],
);

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerConfig: router,
    );
  }
}

class HomePage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Home')),
      body: Center(
        child: ElevatedButton(
          onPressed: () => context.go('/profile/123'),
          child: Text('Go to Profile'),
        ),
      ),
    );
  }
}

class ProfilePage extends StatelessWidget {
  final String userId;

  const ProfilePage({required this.userId});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Profile')),
      body: Center(child: Text('User ID: $userId')),
    );
  }
}

Best Practices

dart
// ✅ Use go() for navigation (declarative)
context.go('/profile');

// ✅ Use named routes for type safety
context.goNamed('profile', pathParameters: {'id': '123'});

// ✅ Use ShellRoute for persistent UI
ShellRoute(builder: ..., routes: [...])

// ✅ Use redirect for auth guards
redirect: (context, state) => isLoggedIn ? null : '/login'

// ❌ Don't mix GoRouter with Navigator.push

Learn more: GoRouter Documentation