Flutter Named Routes and Arguments
Pushing a page with MaterialPageRoute works well for small apps. As the number of screens grows, repeating page classes everywhere becomes messy. Named routes solve this problem by giving each screen a short address, such as /profile, like street names on a city map.
The Route Table
Register every address in one place inside MaterialApp.
MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => HomeScreen(),
'/profile': (context) => ProfileScreen(),
'/settings': (context) => SettingsScreen(),
},
)
Route Table
┌──────────────┬────────────────┐
│ '/' │ HomeScreen │
│ '/profile' │ ProfileScreen │
│ '/settings' │ SettingsScreen │
└──────────────┴────────────────┘
The initialRoute property names the screen that opens first. Remove the home property when you use initialRoute, because the two conflict.
Moving Between Screens
Call pushNamed to open a screen and pop to return.
ElevatedButton(
onPressed: () {
Navigator.pushNamed(context, '/profile');
},
child: Text('Open Profile'),
)
// Back button action
Navigator.pop(context);
Other Navigation Calls
| Method | Effect |
|---|---|
| pushNamed | Adds a screen on top of the stack |
| pop | Removes the top screen |
| pushReplacementNamed | Swaps the current screen for a new one |
| pushNamedAndRemoveUntil | Opens a screen and clears older ones |
Stack after pushNamed('/profile')
┌──────────────┐ ← top (visible)
│ ProfileScreen│
├──────────────┤
│ HomeScreen │
└──────────────┘
Use pushReplacementNamed after login, so the back button never returns to the login page.
Passing Arguments
Screens often need data: which product to show, which user to open. Send the data with the arguments parameter.
Navigator.pushNamed(
context,
'/product',
arguments: 'Blue Headphones',
);
The receiving screen reads the data from the current route settings.
class ProductScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final name = ModalRoute.of(context)!.settings.arguments as String;
return Scaffold(
appBar: AppBar(title: Text(name)),
body: Center(child: Text('Details for $name')),
);
}
}
Passing Several Values
Create a small class to carry more than one value. A class keeps types clear and prevents typos that a map would allow.
class ProductArgs {
final String name;
final double price;
ProductArgs(this.name, this.price);
}
Navigator.pushNamed(context, '/product', arguments: ProductArgs('Headphones', 49.99));
final args = ModalRoute.of(context)!.settings.arguments as ProductArgs;
onGenerateRoute
The routes table cannot read arguments before building a screen. The onGenerateRoute callback receives the route settings and builds the screen with arguments passed straight into the constructor.
onGenerateRoute: (settings) {
if (settings.name == '/product') {
final args = settings.arguments as ProductArgs;
return MaterialPageRoute(
builder: (context) => ProductScreen(args: args),
);
}
return null;
},
This approach gives screens clean constructors and makes them easier to test.
Handling Unknown Routes
A mistyped route name crashes the app. Provide a fallback page with onUnknownRoute.
onUnknownRoute: (settings) {
return MaterialPageRoute(builder: (context) => NotFoundScreen());
},
Returning Data from a Screen
A screen sends a result back through pop. The caller waits for the answer with await.
// Caller
final color = await Navigator.pushNamed(context, '/colorPicker');
print('User chose: $color');
// Colour picker screen
Navigator.pop(context, 'Red');
Keep Route Names Safe
Store names as constants to avoid spelling errors.
class Routes {
static const home = '/';
static const profile = '/profile';
static const product = '/product';
}
Navigator.pushNamed(context, Routes.profile);
A typo in a constant name fails at compile time, while a typo inside a string fails only when a user taps the button.
When Named Routes Fit
Named routes suit apps with ten to twenty screens and simple navigation. The route table doubles as a map of the entire app, so a new teammate understands the screen list in one glance. Changing a screen class affects one table entry instead of every button that opens it.
Apps that need web URLs, deep links, or login guards outgrow this approach. The go_router package covers those needs and builds on the same idea of addresses for screens.
Practice Ideas
Build a three-screen app with Home, Product List, and Product Detail pages. Send the product name and price from the list to the detail screen through a small arguments class. Add a fallback page for unknown routes and test it with a mistyped name.
Reading the Stack in Your Head
Every push places a screen on top of a pile, and every pop removes the top screen. Picture a stack of plates in a cafeteria: the user always sees the plate on top, and the back button lifts it away. Keeping this mental image helps you predict what happens after login, logout, and checkout flows. Draw the stack on paper before you write navigation code for a complex flow, and the right method name becomes obvious.
