I have written a small test app to explore named routing with arguments using ModalRoute.of()
as explained in the Flutter Docs. My test app consists of an initial page (MyFirstPage()
) and four additional pages (e.g. MySecondPage()
, MyThirdPage()
, etc.). Everything is working fine when I route from MyFirstPage()
to any of these other pages.
However, I would like to add a button to MySecondPage()
to route the user back to MyFirstPage()
using the same recipe. When I try to do this, I get an error:
"type 'Null' is not a subtype of type 'FirstPageArguments' in type cast"
where FirstPageArguments()
is a class that defines the arguments I'd like to pass in the route:
class FirstPageArguments {
final String title;
final String message;
FirstPageArguments(this.title, this.message);
}
My code follows ...
MyFirstPage()
import 'package:flutter/material.dart';
import 'page2.dart';
import 'page3.dart';
import 'page4.dart';
import 'page5.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.orange,
),
//home: const MyFirstPage(title: 'Named Routes'),
//initialRoute: '/',
routes: {
// register the page widgets in the routes table
MyFirstPage.routeName: (context) => const MyFirstPage(),
MySecondPage.routeName: (context) => const MySecondPage(),
MyThirdPage.routeName: (context) => const MyThirdPage(),
MyFourthPage.routeName: (context) => const MyFourthPage(),
MyFifthPage.routeName: (context) => const MyFifthPage(),
},
);
}
}
class MyFirstPage extends StatefulWidget {
//final String title;
static const routeName = '/';
//const MyFirstPage({super.key, required this.title});
const MyFirstPage({super.key});
@override
State<MyFirstPage> createState() => _MyFirstPageState();
}
class _MyFirstPageState extends State<MyFirstPage> {
@override
Widget build(BuildContext context) {
String title;
final args =
ModalRoute.of(context)!.settings.arguments as FirstPageArguments;
if (args == null) {
title = 'Page 1';
} else {
title = args.title;
}
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const SizedBox(
height: 30.0,
child: Text(
'Flutter Named Routes',
style: TextStyle(fontSize: 20.0),
),
),
//
// PAGE 2 BUTTON - navigate to page 2 widget
//
ElevatedButton(
onPressed: () {
String title = 'Page 2';
String message = 'Welcome to page 2.';
Navigator.pushNamed(
context,
MySecondPage.routeName,
arguments: SecondPageArguments(
title,
message,
),
);
},
child: const Text('Page 2'),
),
//
// PAGE 3 BUTTON - navigate to page 3 widget
//
ElevatedButton(
onPressed: () {
String title = 'Page 3';
String message = 'Welcome to page 3.';
Navigator.pushNamed(
context,
MyThirdPage.routeName,
arguments: ThirdPageArguments(
title,
message,
),
);
},
child: const Text('Page 3'),
),
//
// PAGE 4 BUTOON - navigate to page 4 widget
//
ElevatedButton(
onPressed: () {
String title = 'Page 4';
bool status = true;
Navigator.pushNamed(
context,
MyFourthPage.routeName,
arguments: FourthPageArguments(title, status),
);
},
child: const Text('Page 4'),
),
//
// PAGE 5 BUTOON - navigate to page 5 widget
//
ElevatedButton(
onPressed: () {
String title = 'Page 5';
String message = 'Welcome to page 5.';
Navigator.pushNamed(
context,
MyFifthPage.routeName,
arguments: FifthPageArguments(title, message),
);
},
child: const Text('Page 5'),
),
// SizedBox(
// Text(args.message);
// ),
],
),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
MySecondPage()
import 'package:flutter/material.dart';
import 'main.dart';
class MySecondPage extends StatelessWidget {
const MySecondPage({super.key});
static const routeName = '/secondPage';
@override
Widget build(BuildContext context) {
// Extract the arguments from the current ModalRoute
// settings and cast them as SecondPageArguments
final args =
ModalRoute.of(context)!.settings.arguments as SecondPageArguments;
return Scaffold(
appBar: AppBar(
title: Text(args.title),
),
// body: Center(
// child: Text(args.message),
// ),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SizedBox(
height: 30.0,
child: Text(args.message, style: const TextStyle(fontSize: 20.0)),
),
ElevatedButton(
onPressed: () {
String title = 'Page 1';
String message = 'Returning from second page.';
Navigator.pushNamed(
context,
MyFirstPage.routeName,
arguments: FirstPageArguments(
title,
message,
),
);
},
child: const Text('Return'),
),
],
),
),
);
}
}
class SecondPageArguments {
final String title;
final String message;
SecondPageArguments(this.title, this.message);
}
I suspect there is a problem here:
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.orange,
),
//home: const MyFirstPage(title: 'Named Routes'),
//initialRoute: '/',
routes: {
// register the page widgets in the routes table
MyFirstPage.routeName: (context) => const MyFirstPage(),
MySecondPage.routeName: (context) => const MySecondPage(),
MyThirdPage.routeName: (context) => const MyThirdPage(),
MyFourthPage.routeName: (context) => const MyFourthPage(),
MyFifthPage.routeName: (context) => const MyFifthPage(),
},
);
}
}
I have attempted to comment out home:
and initialRoute:
so I can register:
MyFirstPage.routeName: (context) => const MyFirstPage(),
MyFirstPage()
widget in the routes:
table. Frankly, I'm not sure how to handle this part.
Can someone please show me the correct way to route the user back from MySecondPage()
to MyFirstPage()
with the attributes shown here using ModalRoute.of()
?
CodePudding user response:
You can just use Navigator.pop(context);
to pop into the previous route.
Or you can also use named pop by Navigator.popUntil(context, ModalRoute.withName(MyFirstPage.routeName));
CodePudding user response:
After some additional troubleshooting, I was able to verify that the source of error is indeed related to the initialization of the variables in FirstPageArguments()
.
class FirstPageArguments {
final String title;
final String message;
FirstPageArguments(this.title, this.message);
}
FirstPageArguments()
is initially null, but it is structured to not be nullable. To fix this problem, I made FirstPageArguments()
nullable:
final args = ModalRoute.of(context)!.settings.arguments as FirstPageArguments?;
This allows me to write a simple conditional to test args
to see if it's null.
if (args == null) {
title = 'Page 1';
message = '';
} else {
title = args.title;
message = args.message;
}
In the case where args
is null, I simply assign initialization values to title
and message
.
Thus, MyFirstPage()
now becomes:
class MyFirstPage extends StatefulWidget {
static const routeName = '/';
const MyFirstPage({super.key});
@override
State<MyFirstPage> createState() => _MyFirstPageState();
}
class _MyFirstPageState extends State<MyFirstPage> {
@override
Widget build(BuildContext context) {
String title;
String message;
final args =
ModalRoute.of(context)!.settings.arguments as FirstPageArguments?;
// handle the case where title and message are initially null due to
// ModalRoute not yet being called (make FirstPageArguments nullable above)
// NOTE: this only affects MyFirstPage().
if (args == null) {
title = 'Page 1';
message = '';
} else {
title = args.title;
message = args.message;
}
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
margin: const EdgeInsets.all(8.0),
child: const Text(
'Flutter Named Routes',
style: TextStyle(fontSize: 20.0),
),
),
//
// PAGE 2 BUTTON - navigate to page 2 widget
//
Container(
margin: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: () {
String title = 'Page 2';
String message = 'Welcome to Page 2';
Navigator.pushNamed(
context,
MySecondPage.routeName,
arguments: SecondPageArguments(
title,
message,
),
);
},
child: const Text('Page 2'),
),
),
//
// PAGE 3 BUTTON - navigate to page 3 widget
//
Container(
margin: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: () {
String title = 'Page 3';
String message = 'Welcome to Page 3';
Navigator.pushNamed(
context,
MyThirdPage.routeName,
arguments: ThirdPageArguments(
title,
message,
),
);
},
child: const Text('Page 3'),
),
),
//
// PAGE 4 BUTOON - navigate to page 4 widget
//
Container(
margin: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: () {
String title = 'Page 4';
bool status = true;
Navigator.pushNamed(
context,
MyFourthPage.routeName,
arguments: FourthPageArguments(title, status),
);
},
child: const Text('Page 4'),
),
),
//
// PAGE 5 BUTOON - navigate to page 5 widget
//
Container(
margin: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: () {
String title = 'Page 5';
String message = 'Welcome to Page 5';
Navigator.pushNamed(
context,
MyFifthPage.routeName,
arguments: FifthPageArguments(title, message),
);
},
child: const Text('Page 5'),
),
),
Container(
margin: const EdgeInsets.all(8.0),
child: Text(
message,
style: const TextStyle(
fontSize: 20.0,
),
),
),
],
),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
The button that returns the user to MyFirstPage()
is written this way:
ElevatedButton(
onPressed: () {
String title = 'Page 1';
String message = 'Returning from second page.';
Navigator.pushNamed(
context,
MyFirstPage.routeName,
arguments: FirstPageArguments(
title,
message,
),
);
},
child: const Text('Return'),
),
The entire app is available here.