In Flutter, we can use Navigation.push
to navigate between screens. That function has two parameters, the first is Context
and the second is WidgetBuilder
. The problem of using Navigation.push
is if there's a screen that can be accessed from multiple screens, it will lead to code duplication because we have to define the same WidgetBuilder
function multiple times. An easy and clean solution to overcome that problem is by using named routes - just define a list of route names along with WidgetBuilder
function for each, then use the name of the route to execute the function
First, create three simple screens: HomeScreen
, SecondScreen
and ThirdScreen
. For this tutorial, we assume that SecondScreen
and ThirdScreen
are on the same level, which means if you navigate from HomeScreen
to SecondScreen
, then to ThirdScreen
, the previous screen on SecondScreen
and ThirdScreen
is the same: HomeScreen
. The HomeScreen
contains a button for navigating to SecondScreen
. The SecondScreen
has two buttons, the upper is for navigating to the ThirdScreen
, while the second buton is for returning to the previous screen. The ThirdScreen
has two buttons, the upper is for navigating to the SecondScreen
, while the second button is for returning to the previous screen. We will add the code inside onPressed
later.
Create the Screens
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home Screen'),
),
body: Center(
child: RaisedButton(
child: Text('Go to second screen'),
onPressed: () {
},
),
),
);
}
}
class SecondScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Screen"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
RaisedButton(
child: Text('Go to third screen'),
onPressed: () {
},
),
RaisedButton(
child: Text('Pop!'),
onPressed: () {
},
),
],
)
),
);
}
}
class ThirdScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Third Screen"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
RaisedButton(
child: Text('Go to second screen'),
onPressed: () {
},
),
RaisedButton(
child: Text('Pop!'),
onPressed: () {
},
),
],
)
),
);
}
}
Define Routes
To use named routes, we need to define the list of routes. Defining routes using MaterialApp
is quite easy. On the constructor, pass routes
option, which is an object whose key is the route name and the value is a WidgetBuilder
function that will be executed if the route name matches. In this case, the function calls the screen that will be launched, as examplified below. You can define as many routes as you want, but there is one required route you have to define: /
. For specifying the first active route when the application launches, use initialState
.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => HomeScreen(),
'/second': (context) => SecondScreen(),
'/third': (context) => ThirdScreen(),
},
);
}
}
Push
The navigation history is saved in a stack. If you want to add the new screen to the stack while navigating, use Navigator.pushNamed
function, with context
as the first argument and the route name as the second argument.
Navigator.pushNamed(context, '/second');
Pop
To pop a screen from history stack, use Navigator.pop
with context
as the first argument.
Navigator.pop(context);
Pop and Push Named
If we want to make SecondScreen
and ThirdScreen
as screens with the same level, the expected behavior is SecondScreen
popped from the stack and the ThirdScreen
pushed to the stack. It can be done using Navigation.popAndPushNamed
with context
as the first argument. The animations for the pop and the push are performed simultaneously, so the route below (in this case HomeScreen
may be briefly visible even if both the old route and the new route are opaque.
Navigator.popAndPushNamed(context);
Push Replacement Named
pushReplacementNamed
is very similar to popAndPushNamed
. The difference is the removed route's exit animation is not run.
Navigator.pushReplacementNamed(context);
Below is the full code.
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home Screen'),
),
body: Center(
child: RaisedButton(
child: Text('Go to second screen'),
onPressed: () {
Navigator.pushNamed(context, '/second');
},
),
),
);
}
}
class SecondScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Screen"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
RaisedButton(
child: Text('Go to third screen'),
onPressed: () {
Navigator.popAndPushNamed(context, '/third');
},
),
RaisedButton(
child: Text('Pop!'),
onPressed: () {
Navigator.pop(context);
},
),
],
)
),
);
}
}
class ThirdScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Third Screen"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
RaisedButton(
child: Text('Go to second screen'),
onPressed: () {
Navigator.pushReplacementNamed(context, '/second');
},
),
RaisedButton(
child: Text('Pop!'),
onPressed: () {
Navigator.pop(context);
},
),
],
)
),
);
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => HomeScreen(),
'/second': (context) => SecondScreen(),
'/third': (context) => ThirdScreen(),
},
);
}
}
Future<void> main() async {
runApp(MyApp());
}