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 the click 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; and 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 necessarily need to start with /
, and 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 jumping, in the previous direct jump example, the method used was Navigator.push
; while here, Navigator.pushNamed
is used.
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 is 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, a ??
is added 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 to either declare it as a nullable type and add a check when using it, or 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 return a value, and the return value is of type Future
. When calling the Navigator.Pop
method, if the second optional parameter is passed with content, 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 use of the button click method above, which encapsulates a _pagePush
method separately and uses the async
modifier, 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 ReactNative, you should be familiar with this kind of writing.
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 New Page to Previous Page
Future class