flutter
/

Flutter Named Routes – Organize Navigation with Route Names

Last Sync: Today

On this page

9
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutter

Flutter Named Routes – Organize Navigation with Route Names

What are Named Routes?

In Flutter, navigation is typically done using Navigator.push with a MaterialPageRoute. For larger apps, this can lead to code that is hard to read and maintain. Named routes allow you to define a map of route names to widgets, then navigate using Navigator.pushNamed. This centralises your route definitions and makes it easier to pass arguments, handle deep linking, and manage app structure.

Defining Named Routes in MaterialApp

You define named routes in the routes property of MaterialApp. It's a Map<String, WidgetBuilder>, where the key is the route name (e.g., '/detail') and the value is a function that returns a widget. The initial route is set with initialRoute.

DARTRead-only
1
MaterialApp(
  initialRoute: '/',
  routes: {
    '/': (context) => HomeScreen(),
    '/detail': (context) => DetailScreen(),
    '/settings': (context) => SettingsScreen(),
  },
)

Now you can navigate using Navigator.pushNamed(context, '/detail').

Passing Arguments with Named Routes

Unlike direct MaterialPageRoute, pushNamed allows you to pass arguments using the arguments parameter. You retrieve them in the destination using ModalRoute.of(context)!.settings.arguments.

DARTRead-only
1
// Pushing
Navigator.pushNamed(
  context,
  '/detail',
  arguments: 'Hello from home',
);

// Retrieving in DetailScreen
final args = ModalRoute.of(context)!.settings.arguments as String;

Handling Dynamic Routes with onGenerateRoute

For routes that need to accept parameters (e.g., /product/123), you can't use the static routes map. Instead, use onGenerateRoute. This function is called when a named route is pushed but not found in the static routes map. You can parse the route name and return a MaterialPageRoute with the appropriate widget and arguments.

DARTRead-only
1
MaterialApp(
  initialRoute: '/',
  onGenerateRoute: (settings) {
    if (settings.name == '/') {
      return MaterialPageRoute(builder: (_) => HomeScreen());
    }
    if (settings.name == '/detail') {
      return MaterialPageRoute(
        builder: (_) => DetailScreen(),
        settings: settings, // passes arguments through
      );
    }
    if (settings.name?.startsWith('/product/') == true) {
      final id = int.parse(settings.name!.substring('/product/'.length));
      return MaterialPageRoute(
        builder: (_) => ProductDetailScreen(productId: id),
      );
    }
    return null; // Let the system handle error
  },
)

You can also combine routes and onGenerateRoute: static routes go in routes, dynamic ones handled by onGenerateRoute.

Handling Unknown Routes (404)

If a route is not found, you can show a fallback screen using the unknownRoute property. This is useful for handling deep links or user‑typed URLs.

DARTRead-only
1
MaterialApp(
  unknownRoute: MaterialPageRoute(
    builder: (_) => NotFoundScreen(),
  ),
)

Using named routes with arguments in a typed way

To avoid casting everywhere, you can create a utility class that holds the route names and argument types, or use a package like go_router. For a simple app, you can define a class with constants and a helper method to push with arguments.

DARTRead-only
1
class Routes {
  static const String home = '/';
  static const String detail = '/detail';

  static void goToDetail(BuildContext context, String message) {
    Navigator.pushNamed(context, detail, arguments: message);
  }
}

Common Mistakes

    • Forgetting to include a leading slash: Route names should start with / (e.g., /detail).
    • Using pushNamed without a corresponding entry: If you don't define the route in routes or onGenerateRoute, it will throw an error.
    • Not handling arguments: If you push with arguments but don't read them, you'll miss data.
    • Ignoring onGenerateRoute for dynamic routes: If your app has many dynamic routes, they should be handled in onGenerateRoute to keep routes clean.
    • Not handling the unknownRoute: A 404 screen improves UX and prevents crashes.

Best Practices

    • Use constants for route names to avoid typos.
    • Keep the route definition map organised – maybe in a separate file.
    • For dynamic routes, prefer onGenerateRoute over a large routes map.
    • Use typed arguments to avoid casting errors – create a custom class or use a package like go_router for complex navigation.
    • Always provide an unknownRoute to handle deep links gracefully.

Key Takeaways

    • Named routes are defined in MaterialApp's routes map.
    • Use Navigator.pushNamed with an optional arguments parameter.
    • Retrieve arguments with ModalRoute.of(context)!.settings.arguments.
    • For dynamic routes (e.g., /product/123), use onGenerateRoute.
    • Handle unknown routes with unknownRoute.
    • Centralising routes makes navigation easier to maintain.

Try it yourself

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: '/',
      routes: {
        '/': (context) => HomeScreen(),
        '/detail': (context) => DetailScreen(),
      },
      onGenerateRoute: (settings) {
        // Handle dynamic routes
        if (settings.name == '/profile') {
          final args = settings.arguments as String?;
          return MaterialPageRoute(
            builder: (_) => ProfileScreen(userName: args ?? 'Guest'),
          );
        }
        return null;
      },
      unknownRoute: MaterialPageRoute(
        builder: (_) => NotFoundScreen(),
      ),
    );
  }
}

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Home')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () {
                Navigator.pushNamed(
                  context,
                  '/detail',
                  arguments: 'Hello from Home',
                );
              },
              child: Text('Go to Detail (with arguments)'),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                Navigator.pushNamed(
                  context,
                  '/profile',
                  arguments: 'Alice',
                );
              },
              child: Text('Go to Profile (dynamic route)'),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                Navigator.pushNamed(context, '/unknown');
              },
              child: Text('Trigger 404'),
            ),
          ],
        ),
      ),
    );
  }
}

class DetailScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final args = ModalRoute.of(context)!.settings.arguments as String?;
    return Scaffold(
      appBar: AppBar(title: Text('Detail')),
      body: Center(
        child: Text('Received: ${args ?? 'no arguments'}'),
      ),
    );
  }
}

class ProfileScreen extends StatelessWidget {
  final String userName;
  ProfileScreen({required this.userName});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Profile')),
      body: Center(
        child: Text('Hello, $userName'),
      ),
    );
  }
}

class NotFoundScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Not Found')),
      body: Center(
        child: Text('The page you are looking for does not exist.'),
      ),
    );
  }
}

Test Your Knowledge

Q1
of 4

Which property of MaterialApp is used to define a static mapping of route names to widgets?

A
routes
B
onGenerateRoute
C
initialRoute
D
unknownRoute
Q2
of 4

How do you pass arguments to a named route?

A
In the `builder` of the route definition
B
Using the `arguments` parameter of `pushNamed`
C
Using a global variable
D
You cannot pass arguments to named routes
Q3
of 4

How do you retrieve arguments in the destination screen?

A
Route.of(context).arguments
B
ModalRoute.of(context)!.settings.arguments
C
context.arguments
D
Navigator.arguments
Q4
of 4

What property is called when a named route is not found in the `routes` map?

A
unknownRoute
B
onGenerateRoute
C
errorBuilder
D
notFoundRoute

Frequently Asked Questions

Can I have nested named routes?

Yes, you can use Navigator inside a widget to create a nested navigation stack. The named routes defined at the top level are for the root navigator. For nested navigators, you need to define their own onGenerateRoute or use packages like go_router.

How do I pass arguments with `pushNamedAndRemoveUntil`?

The arguments parameter is available in pushNamedAndRemoveUntil as well. Example: Navigator.pushNamedAndRemoveUntil(context, '/home', (route) => false, arguments: myData);

Can I use `pushReplacementNamed` with arguments?

Yes, pushReplacementNamed also accepts an arguments parameter. For example: Navigator.pushReplacementNamed(context, '/login', arguments: userData);

What's the difference between `pushNamed` and `pushReplacementNamed`?

pushNamed adds a new route on top of the current one, allowing the user to go back. pushReplacementNamed replaces the current route with the new one, so the previous route is removed from the stack.

How do I define named routes for a bottom navigation bar?

You can have a root screen that contains a BottomNavigationBar and an IndexedStack, and then use named routes for pages that are not part of the main tabs (e.g., detail pages). Alternatively, use a Navigator inside each tab to have independent navigation stacks.

Previous

flutter gradient

Next

flutter navigation arguments

Related Content

Need help?

Explore our comprehensive docs or start a chat with our tech experts.