Flutter Layout Basics - Page Navigation and Passing Values#
When it comes to navigation, the most common effect is similar to the push and pop effects of the navigation controller in iOS. Flutter also has similar effects, using the Navigator
component.
Next, let's take a look at the usage of navigation effects Navigator.push
and Navigator.pop
in Flutter.
Simple Usage#
The code is as follows:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Navigation Demo 1',
home: new FirstScreen(),
);
}
}
class FirstScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: Text('Navigation Page'),
),
body: Center(
child: ElevatedButton(
child: Text('View Product Details Page'),
onPressed: () {
Navigator.push(context,
new MaterialPageRoute(builder: (context) => new SecondScreen()));
},
)),
);
}
}
class SecondScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('SecondScreen'),
),
body: Center(
child: OutlinedButton(
child: Text('Return'),
onPressed: () {
Navigator.pop(context);
},
),
),
);
}
}
Note that when using Navigator.push
, the page to be navigated to is wrapped in MaterialPageRoute
. The SecondScreen
in the code adds a button, and the implementation method for clicking is Navigator.pop
, which is used to return; however, usually, there is no need to specifically implement Navigator.pop
, because in iOS, when using AppBar
, a return button is automatically added to the top left corner; in Android, the system back button can also directly return.
Named Navigation#
Similar to route jumping, navigation can be done using names instead of class names.
The following code defines three pages, MyFirstPage
is the home page, and MySecondPage
and MyThirdPage
are two different pages. There are two buttons on the home page, corresponding to jumping to MySecondPage
and MyThirdPage
, while passing parameters during the jump, which are displayed on the corresponding pages.
The code is as follows:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyFirstPage(),
routes: <String, WidgetBuilder>{
'/a': (BuildContext context) => MySecondPage(title: 'Page A'),
'b': (BuildContext context) => MyThirdPage(title: 'Page B'),
},
);
}
}
class MyFirstPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('homepage'),
),
body: Center(
child: Column(
children: [
OutlinedButton(
onPressed: () {
Navigator.pushNamed(context, '/a');
},
child: new Text('PageA'),
),
OutlinedButton(
onPressed: () {
Navigator.pushNamed(context, 'b');
},
child: new Text('PageB'),
),
],
),
),
);
}
}
class MySecondPage extends StatelessWidget {
final String title;
const MySecondPage({Key? key, required this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Second Page'),
),
body: Center(
child: OutlinedButton(
onPressed: () {
Navigator.pop(context);
},
child: new Text(this.title),
),
),
);
}
}
class MyThirdPage extends StatelessWidget {
final String? title;
const MyThirdPage({Key? key, this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Third Page'),
),
body: Center(
child: new OutlinedButton(
onPressed: () {
Navigator.pop(context);
},
child: new Text(this.title ?? 'Default PageName')),
),
);
}
}
There are a few points to note here:
First, the use of routes
, which is of type <String, WidgetBuilder>
, where the former is the name of the page to jump to and the latter is the corresponding page to jump to. Moreover, the name of the page to jump to does not have to start with /
, so it can be defined freely, but according to routing understanding, it is still recommended to follow a unified naming convention.
Secondly, regarding the use of Navigator
for navigation, in the previous direct jump example, the method used was Navigator.push
; while here, it is Navigator.pushNamed
.
Finally, it is important to note how to pass values between pages.
Passing Values Between Pages#
Just like in iOS development, passing values between pages can be divided into passing values from the upper-level page to the lower-level page and callback values from the lower-level page to the upper-level page.
Passing Values from Upper-Level Page to Lower-Level Page#
The above code passes values from the upper-level page to the lower-level page, but the implementations of MySecondPage
and MyThirdPage
are different, as shown below:
There are two differences: initial declaration and specific usage;
In MySecondPage
, the declared title
property is a non-nullable String, using the required
modifier (note that it is required
and not @required
, as some articles have not been updated), and it is used directly.
In MyThirdPage
, the declared title
property is a nullable String, without using the required
modifier, but when used, it adds ??
to provide a default value.
If confused, you may encounter the error The parameter 'title' can't have a value of 'null' because of its type, but the implicit default value is 'null'. Try adding either an explicit non-'null' default value or the 'required' modifier.dart(missing_default_value_for_parameter)
, which is due to the difference between nullable and non-nullable types. The solution is either to declare it as a nullable type and add a check when using it, or to use the required
modifier to declare it as non-nullable.
Passing Values from Lower-Level Page to Upper-Level Page#
The Navigator.Push
method can have a return value, and the return value is of type Future
. When calling the Navigator.Pop
method, if the second optional parameter is passed in, it will be returned in Navigator.Push
.
The code is as follows:
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyFirstPage(),
routes: <String, WidgetBuilder>{
'/a': (BuildContext context) => MySecondPage(title: 'Page A'),
'b': (BuildContext context) => MyThirdPage(title: 'Page B'),
},
);
}
}
class MyFirstPage extends StatelessWidget {
String callbackText = '';
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('homepage'),
),
body: Center(
child: Column(
children: [
OutlinedButton(
onPressed: () {
_pagePush(context, '/a');
},
child: new Text('PageA'),
),
OutlinedButton(
onPressed: () {
// Navigator.pushNamed(context, 'b');
_pagePush(context, 'b');
},
child: new Text('PageB'),
),
Text(callbackText),
],
),
),
);
}
}
_pagePush(BuildContext context, String target) async {
final result = await Navigator.pushNamed(context, target);
ScaffoldMessenger.of(context).showSnackBar(
new SnackBar(content: Text("$result")),
);
}
class MySecondPage extends StatelessWidget {
final String title;
const MySecondPage({Key? key, required this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Second Page'),
),
body: Center(
child: OutlinedButton(
onPressed: () {
Navigator.pop(context, 'SecondPage Callback');
},
child: new Text(this.title),
),
),
);
}
}
class MyThirdPage extends StatelessWidget {
final String? title;
const MyThirdPage({Key? key, this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Third Page'),
),
body: Center(
child: new OutlinedButton(
onPressed: () {
Navigator.pop(context, 'ThirdPage Callback');
},
child: new Text(this.title ?? 'Default PageName')),
),
);
}
}
The effect is as follows:
Note the usage of the button click methods above, where a separate _pagePush
method is encapsulated, and it is marked with async
because the return value of Navigator.push
is of type Future
, which needs to be used with await
, and await
can only be used in methods marked with async
. If you have written React Native, you should be familiar with this syntax.
References#
Navigator Dev Doc
Flutter Free Video Season 4 - Page Navigation and Others
The parameter can't have a value of 'null' because of its type in Dart
Returning data from a new page to the previous page
Future class