home / skills / yelmuratoff / agent_sync / routing

This skill helps you implement robust Flutter navigation with GoRouter, enabling nested routes, deep links, and type-safe arguments across screens.

npx playbooks add skill yelmuratoff/agent_sync --skill routing

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
3.0 KB
---
name: routing
description: When adding screens, deep links, or complex navigation flows using `go_router`.
---

# Routing (GoRouter)

## When to use

- Adding a new screen or feature entry point.
- Implementing deep linking or redirection (e.g., AuthGuard).
- Passing arguments between screens.

## Setup

Define a centralized `local_router.dart` (or similar) in `core/router/` or `app/router/`.
Prefer nested/sub-routes over a flat list so deep links and back navigation remain predictable.

```dart
final goRouter = GoRouter(
  initialLocation: '/',
  debugLogDiagnostics: true,
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => const HomePage(),
      routes: [
        GoRoute(
          path: 'details/:id',
          builder: (context, state) {
            final id = state.pathParameters['id']!;
            return DetailPage(id: id);
          },
        ),
      ],
    ),
  ],
);
```

## Best Practices

### 1) Type-Safe Arguments

**Preferred: `go_router_builder` with `@TypedGoRoute`**

When `go_router_builder` is available, annotate route data classes:

```dart
@TypedGoRoute<OrdersRoute>(name: 'orders', path: '/orders')
@immutable
class OrdersRoute extends GoRouteData {
  const OrdersRoute();

  @override
  Widget build(BuildContext context, GoRouterState state) {
    return const OrdersScreen();
  }
}

// With parameters:
@TypedGoRoute<OrderDetailRoute>(name: 'order-detail', path: '/orders/:id')
@immutable
class OrderDetailRoute extends GoRouteData {
  const OrderDetailRoute({required this.id});
  final String id;

  @override
  Widget build(BuildContext context, GoRouterState state) {
    return OrderDetailScreen(id: id);
  }
}

// Navigate:
const OrdersRoute().go(context);
OrderDetailRoute(id: orderId).go(context);
```

**Fallback (no go_router_builder)**: Use `goNamed` with string constants defined in a central route-names file. Never inline path strings in widget code.

- Use path parameters for IDs (e.g. `details/:id`).
- Use query parameters for filtering/sorting state (for example: `?status=paid&page=2`).
- Use `extra` for complex objects **only if necessary**. Prefer passing an ID and refetching data to ensure the screen is independent and deep-linkable.
- Prefer typed routes or `goNamed` over raw path strings where possible.
- Keep route path segments lowercase `kebab-case` (for example `user/update-address`).

### 2) Redirects (Guards)

- implement `redirect` logic at the top level or per-route.

```dart
redirect: (context, state) {
  final isLoggedIn = authBloc.state.isAuthenticated;
  final isLoggingIn = state.uri.path == '/login';

  if (!isLoggedIn && !isLoggingIn) return '/login';
  if (isLoggedIn && isLoggingIn) return '/';

  return null;
},
```

### 3) Navigation

- Use `context.go('/details/123')` or `context.goNamed(...)` for normal app/deep-linkable navigation.
- Use `context.push('/details/123')` when the route is transient and should return a result on pop.
- Prefer `BuildContext` extensions (`context.goNamed`, `context.pushNamed`) over `GoRouter.of(context)` for consistency.

Overview

This skill helps integrate and manage navigation using go_router in Flutter apps. It focuses on adding screens, deep links, redirects, and type-safe argument passing so navigation stays predictable and testable. The guidance covers setup, routing patterns, and common pitfalls to avoid.

How this skill works

Define a centralized router object and organize routes with nested sub-routes to preserve back navigation and deep-link behavior. Prefer typed route classes (via go_router_builder/@TypedGoRoute) or named routes for safer navigation and parameter handling. Implement redirect logic at the top level or per-route to enforce auth and other guards.

When to use it

  • Adding a new screen, feature entry point, or nested section
  • Implementing deep links or URL-based navigation for web/mobile
  • Enforcing authentication or feature guards with redirects
  • Passing arguments between screens while keeping deep links valid
  • Refactoring navigation to use typed routes or centralized route names

Best practices

  • Centralize router configuration (e.g., core/router or app/router) and prefer nested routes over flat lists
  • Use go_router_builder with @TypedGoRoute for type-safe route data; fall back to goNamed with constants if not available
  • Pass IDs via path parameters and query parameters for filters; avoid passing large objects in extra unless necessary
  • Implement redirect logic to handle auth and special flows; return null when no redirect is required
  • Use context.go / context.goNamed for main navigation and context.push / context.pushNamed for transient pages that return results

Example use cases

  • Add a product details screen reachable by /products/:id and support direct deep links
  • Protect a settings area behind authentication using a top-level redirect/guard
  • Implement paginated and filterable lists using query parameters like ?status=paid&page=2
  • Navigate from an orders list to an order detail using a typed OrderDetailRoute(id: …).go(context)
  • Open a modal or selection sheet with context.push when you need a return value

FAQ

Should I pass complex objects via extra?

Avoid it when possible. Prefer passing an ID and reloading data so screens are deep-linkable and independent; use extra only for transient state that cannot be refetched.

Where should redirect logic live?

Place auth or global redirect logic at the top-level router or on specific route entries to keep it predictable and centralized.

When to use push vs go?

Use go/goNamed for normal navigation and deep-linkable routes. Use push/pushNamed for transient routes that should return a result on pop.