flutter-implementing-navigation-and-routing

Imperative and declarative routing patterns for Flutter screen transitions and deep linking. Covers both Navigator (imperative, stack-based) and Router (declarative, URL-synchronized) approaches with guidance on when to use each Supports deep linking on iOS, Android, and Web; includes data passing via constructors, route arguments, and return values Implements nested navigation for multi-step flows (e.g., setup wizards) with independent sub-navigators and back-button interception Provides three complete workflows: standard screen transitions, deep-linkable routing setup, and nested navigation flows with code examples

INSTALLATION
npx skills add https://github.com/flutter/skills --skill flutter-implementing-navigation-and-routing
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

$2a

Use the Navigator widget to push and pop routes using platform-specific transition animations (MaterialPageRoute or CupertinoPageRoute).

Pushing and Popping

  • Navigate to a new route using Navigator.push(context, route).
  • Return to the previous route using Navigator.pop(context).
  • Use Navigator.pushReplacement() to replace the current route, or Navigator.pushAndRemoveUntil() to clear the stack based on a condition.

Passing and Returning Data

  • Sending Data: Pass data directly into the constructor of the destination widget. Alternatively, pass data via the settings: RouteSettings(arguments: data) parameter of the PageRoute and extract it using ModalRoute.of(context)!.settings.arguments.
  • Returning Data: Pass the return value to the pop method: Navigator.pop(context, resultData). Await the result on the pushing side: final result = await Navigator.push(...).

Implementing Declarative Navigation

For apps requiring deep linking, web URL support, or complex routing, implement the Router API via a declarative routing package like go_router.

  • Switch from MaterialApp to MaterialApp.router.
  • Define a router configuration that parses route paths and configures the Navigator automatically.
  • Navigate using package-specific APIs (e.g., context.go('/path')).
  • Page-backed vs. Pageless Routes: Declarative routes are page-backed (deep-linkable). Imperative pushes (e.g., dialogs, bottom sheets) are pageless. Removing a page-backed route automatically removes all subsequent pageless routes.

Implementing Nested Navigation

Implement nested navigation to manage a sub-flow of screens (e.g., a multi-step setup process or persistent bottom navigation tabs) independently from the top-level global navigator.

  • Instantiate a new Navigator widget inside the host widget.
  • Assign a GlobalKey<NavigatorState> to the nested Navigator to control it programmatically.
  • Implement the onGenerateRoute callback within the nested Navigator to resolve sub-routes.
  • Intercept hardware back button presses using PopScope to prevent the top-level navigator from popping the entire nested flow prematurely.

Workflows

Workflow: Standard Screen Transition

Copy this checklist to track progress when implementing a basic screen transition:

  • Create the destination widget (Route).
  • Define required data parameters in the destination widget's constructor.
  • Implement Navigator.push() in the source widget.
  • Wrap the destination widget in a MaterialPageRoute or CupertinoPageRoute.
  • Implement Navigator.pop() in the destination widget to return.

Workflow: Implementing Deep-Linkable Routing

Use this conditional workflow when setting up app-wide routing:

  • If the app is simple and requires no deep linking:
  • Use standard MaterialApp and Navigator.push().
  • If the app requires deep linking, web support, or complex flows:
  • Add the go_router package.
  • Change MaterialApp to MaterialApp.router.
  • Define the GoRouter configuration with all top-level routes.
  • Replace Navigator.push() with context.go() or context.push().

Workflow: Creating a Nested Navigation Flow

Run this workflow when building a multi-step sub-flow (e.g., IoT device setup):

  • Define string constants for the nested route paths.
  • Create a GlobalKey<NavigatorState> in the host widget's state.
  • Return a Navigator widget in the host's build method, passing the key.
  • Implement onGenerateRoute in the nested Navigator to map string paths to specific step widgets.
  • Wrap the host Scaffold in a PopScope to handle back-button interceptions (e.g., prompting "Are you sure you want to exit setup?").
  • Use navigatorKey.currentState!.pushNamed() to advance steps within the flow.

Examples

Example: Passing Data via Constructor (Imperative)

// 1. Define the data model

class Todo {

  final String title;

  final String description;

  const Todo(this.title, this.description);

}

// 2. Source Screen

class TodosScreen extends StatelessWidget {

  final List<Todo> todos;

  const TodosScreen({super.key, required this.todos});

  @override

  Widget build(BuildContext context) {

    return Scaffold(

      appBar: AppBar(title: const Text('Todos')),

      body: ListView.builder(

        itemCount: todos.length,

        itemBuilder: (context, index) {

          return ListTile(

            title: Text(todos[index].title),

            onTap: () {

              // Push and pass data via constructor

              Navigator.push(

                context,

                MaterialPageRoute(

                  builder: (context) => DetailScreen(todo: todos[index]),

                ),

              );

            },

          );

        },

      ),

    );

  }

}

// 3. Destination Screen

class DetailScreen extends StatelessWidget {

  final Todo todo;

  const DetailScreen({super.key, required this.todo});

  @override

  Widget build(BuildContext context) {

    return Scaffold(

      appBar: AppBar(title: Text(todo.title)),

      body: Padding(

        padding: const EdgeInsets.all(16),

        child: Text(todo.description),

      ),

    );

  }

}

Example: Nested Navigation Flow

class SetupFlow extends StatefulWidget {

  final String initialRoute;

  const SetupFlow({super.key, required this.initialRoute});

  @override

  State<SetupFlow> createState() => _SetupFlowState();

}

class _SetupFlowState extends State<SetupFlow> {

  final _navigatorKey = GlobalKey<NavigatorState>();

  void _exitSetup() => Navigator.of(context).pop();

  @override

  Widget build(BuildContext context) {

    return PopScope(

      canPop: false,

      onPopInvokedWithResult: (didPop, _) async {

        if (didPop) return;

        // Intercept back button to prevent accidental exit

        _exitSetup();

      },

      child: Scaffold(

        appBar: AppBar(title: const Text('Setup')),

        body: Navigator(

          key: _navigatorKey,

          initialRoute: widget.initialRoute,

          onGenerateRoute: _onGenerateRoute,

        ),

      ),

    );

  }

  Route<Widget> _onGenerateRoute(RouteSettings settings) {

    Widget page;

    switch (settings.name) {

      case 'step1':

        page = StepOnePage(

          onComplete: () => _navigatorKey.currentState!.pushNamed('step2'),

        );

        break;

      case 'step2':

        page = StepTwoPage(onComplete: _exitSetup);

        break;

      default:

        throw StateError('Unexpected route name: ${settings.name}!');

    }

    return MaterialPageRoute(

      builder: (context) => page,

      settings: settings,

    );

  }

}
BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card