Pass data in navigator.pushNamed - flutter

I want to pass data from list_screen to tenancyDetailsScreen, but I stuck at the routes page.
list_screen.dart
Navigator.pushNamed(context, AppRoute.tenancyDetailsScreen,arguments:tenancy[index]);
app_route.dart
class AppRoute {
static const tenancyDetailsScreen = '/tenancyDetailsScreen';
static final _tenancyDetailsScreenRoute = Routes(tenancyDetailsScreen,TenancyDetailsScreen());
static Route? onGenerateRoutes(RouteSettings settings) {
if(settings.name == tenancyDetailsScreen){
final args = settings.arguments as Tenancy; // how to pass this args to DetailsScreen?
return _tenancyDetailsScreenRoute.materialRoute;
}
....
}
}
routes.dart
class Routes extends Equatable {
final String route;
final Widget page;
const Routes(this.route, this.page);
#override
List<Object> get props => [
route,
];
get materialRoute => MaterialPageRoute(
builder: (_) => SafeArea(child: page),
);
}

If you want to pass an arguments, you can't use custom Routes.
static Route? onGenerateRoutes(RouteSettings settings) {
if(settings.name == tenancyDetailsScreen){
final args = settings.arguments as Tenancy; // how to pass this args to DetailsScreen?
return MaterialPageRoute(
builder: (_) => SafeArea(child: TenancyDetailsScreen(tenancy: args)),
);
}
....
}

Related

How to pass in arguments in flutter when user input URL address (using auto_route)

My app uses flutter with auto_router 0.6.9 package, and I want user to be able to type in a web address http://localhost/#/book/123 and be able to access book 123 and display book123, their name, and email if they are logged in. I had figured out the routing part with auto_router but when the user put the address directly into the browser, the arguments should be passed into the page are null.
I was wondering is there a way to parse the url and pass in the arguments required before displaying the page. Thanks for any help or hints!
My router.dart page:
#MaterialAutoRouter(
routes: <AutoRoute>[
// some other pages...
MaterialRoute(path: "/book/:id", page: MultiProvider),
],
)
class $ModularRouter {}
The router.gr.dart generated for multiprovider arguments class is:
/// MultiProvider arguments holder class
class MultiProviderArguments {
final Key key;
final List<SingleChildWidget> providers;
final Widget child;
final Widget Function(BuildContext, Widget) builder;
MultiProviderArguments(
{this.key, #required this.providers, this.child, this.builder});
}
My book menu page contains a navigation:
(this is an example of how I normally call the BookPage in another page)
child: GestureDetector(
onTap: () => ExtendedNavigator.of(context).push(
Routes.multiProvider(id: book[index].bookID),
arguments: MultiProviderArguments(
providers: [
Provider<UserData>.value(
value: userdata,
),
Provider<List<BookInfo>>.value(
value: book,
),
],
child: BookPage(
bookId: book[index].bookID,
name: userdata.userName,
email: userdata.email,
),
),
),
...
And BookPage takes in 3 arguments bookId, name and email:
class BookPage extends StatefulWidget {
final String bookId;
final String name;
final String email;
BookPage({
this.bookId,
this.name,
this.email,
});
...
}
...
class _BookPageState extends State<BookPage> {
...
Widget build(BuildContext context) {
final currentUser = Provider.of<UserData>(context, listen: false);
final currentBook = Provider.of<List<BookInfo>>(context, listen: false);
...
}
}
I had figured out a Band-Aid solution (which I don't consider to be permanent):
I had abandoned using auto-route which I have no idea how to parse the url and use onGenerateRoute instead:
onGenerateRoute: (settings) {
final settingsUri = Uri.parse(settings.name);
final user = Provider.of<User>(context);
if (user == null) {
return MaterialPageRoute(
builder: (context) {
return LogInPage();
}
);
} else {
// Handle '/'
if (settingsUri.pathSegments.length == 0) {
return MaterialPageRoute(
builder: (context) {
return PageWrapper(false, bookid);
}
);
}
// Handle '/book/:id'
if (settingsUri.pathSegments.length == 2) {
if (settingsUri.pathSegments.first != 'book') {
return MaterialPageRoute(
builder: (context) {
return UnknownPage();
}
);
}
final bookid = settingsUri.pathSegments.elementAt(1);
if (bookid == null) {
return MaterialPageRoute(
builder: (context) {
return UnknownPage();
}
);
}
return MaterialPageRoute(
builder: (context) {
return PageWrapper(false, bookid);
}
);
}
}
//Handle other unknown Routes
return MaterialPageRoute(
builder: (context) {
return UnknownPage();
}
);
}
The PageWrapper authenticates user, build providers, and call BookPage class, I put the code in a place where userData providers had already been initialized:
if (this.needRedirect == true) {
return Scaffold(
...
body: FutureBuilder(
future: Future.wait([userData]),
builder: (context, snapshot) {
return MultiProvider(
providers: [
//initialize using streamproviders
...
],
child: BookPage(
bookId: book[index].bookID,
name: userdata.userName,
email: userdata.email,
),
);
}),
);
}
else {...}
The short answer is I had called the WrapperPage in onGenerateRoutes because the providers are not initialized in main.dart, but they are initialized in WrapperPage. Then I put the redirect code after provider initialization in WrapperPage and called BookPage.
This method had caused other bugs, but it had achieved its purpose.

App widget is not generating the route | firebase signup

Upon successful signup, I am trying to send users to the homepage (home) explaining how to use the app. I am doing so through this code block on my signup.dart
onPressed: () async {
try {
User user =
(await FirebaseAuth.instance.createUserWithEmailAndPassword(
email: _emailController.text,
password: _passwordController.text,
))
.user;
if (user != null) {
user.updateProfile(displayName: _nameController.text);
Navigator.of(context).pushNamed(AppRoutes.home);
}
}
Which is pointing to the home route
class AppRoutes {
AppRoutes._();
static const String authLogin = '/auth-login';
static const String authSignUp = '/auth-signup';
static const String home = '/home';
static Map<String, WidgetBuilder> define() {
return {
authLogin: (context) => Login(),
authSignUp: (context) => SignUp(),
home: (context) => Home(),
};
}
}
However, when I sign up, the data is rendering in firebase, but the user is not being sent to the home page, and throws this error in my console
Make sure your root app widget has provided a way to generate
this route.
Generators for routes are searched for in the following order:
1. For the "/" route, the "home" property, if non-null, is used.
2. Otherwise, the "routes" table is used, if it has an entry for the route.
3. Otherwise, onGenerateRoute is called. It should return a non-null value for any valid route not handled by "home" and "routes".
4. Finally if all else fails onUnknownRoute is called.
Unfortunately, onUnknownRoute was not set.
Any thoughts on how to rectify?
Have you added onGenerateRoute in your MaterialApp? Like this:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
onGenerateRoute: Router.generateRoute,
initialRoute: yourRoute,
child: YouApp(),
);
}
}
class Router {
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case AppRoutes.home:
return MaterialPageRoute(builder: (_) => Home());
case AppRoutes.authLogin:
return MaterialPageRoute(builder: (_) => Login());
case AppRoutes.authSignUp:
return MaterialPageRoute(builder: (_) => SignUp());
default:
return MaterialPageRoute(
builder: (_) => Scaffold(
body: Center(
child: Text('No route defined for ${settings.name}')),
));
}
}
}
}
}

Flutter + Get, toNamed() returns RouteSettings() error

I am very new to Flutter so I apologize for not understanding all the terminology.
I have an application that receives data from FCM, and it shows a SnackBar (using Get). All of this is working well. The problem is the 'onTap'. When I use Get.toNamed(), it responds with,
Could not find a generator for route RouteSettings("/home-screen", null) in the _WidgetsAppState.
This is my current Snackbar
void showDialog(BuildContext context, messageJson) {
print('showDialog');
try {
final data = messageJson['data'];
final notification =
data != null && data.keys.isNotEmpty ? data : messageJson['notification'];
var body = notification['body'];
var title = notification['title'];
Get.snackbar(
title,
body,
icon: Icon(Icons.chat),
shouldIconPulse: true,
onTap: (index) {
Get.toNamed('/home-screen');
//Navigator.of(context).pushNamed('/home-screen'); // <-- Didn't work either
},
isDismissible: true,
duration: Duration(seconds: 4),
);
} catch (e) {
print(e.toString());
}
}
With my Routes being setup like this.
class Routes {
static final Map<String, WidgetBuilder> _routes = {
"/home-screen": (context) => HomeScreen(),
"/home": (context) => MainTabs(),
"/login": (context) => LoginScreen(),
"/register": (context) => RegistrationScreen(),
...VendorRoute.getAll(),
};
static Map<String, WidgetBuilder> getAll() => _routes;
static WidgetBuilder getRouteByName(String name) {
if (_routes.containsKey(name) == false) {
return _routes[RouteList.homeScreen];
}
return _routes[name];
}
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case RouteList.storeDetail:
return MaterialPageRoute(
builder: VendorRoute.getRoutesWithSettings(settings)[settings.name],
);
default:
return null;
}
}
}
I'm at a standstill on trying to figure out how to navigate to a page onTap()
I have tried all the other variations as well. Any help would be appreciated here. Thanks!
REF: Flutter Get Package, https://pub.dev/packages/get
Did you set routes and onGenerateRoute inside MaterialApp? Here is an example https://flutter.dev/docs/cookbook/navigation/named-routes

How can a named route have URL parameters in flutter web?

I'm building a web-app which needs to have a route that gets a post ID and then it will fetch the post using the ID.
How can I have URL arguments let's say /post/:id so id is the argument
My app looks like that currently:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
// title: "Paste",
initialRoute: "/",
theme: ThemeData(
primarySwatch: Colors.green,
primaryColor: Colors.blue
),
routes: {
"/": (context) => HomePage(),
"/post": (context) => PastieRoute()
},
debugShowCheckedModeBanner: false
);
}
}
EDIT:
This is what I tried according to #BloodLoss and for some reason I don't get anything to the console when accessing localhost:8080/post?id=123
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: "/",
routes: {
"/": (context) => HomePage(),
"/post": (context) => PastieRoute()
},
onGenerateRoute: (settings) {
if (settings.name == "/post") {
print(settings.arguments); // Doesn't fire :(
return MaterialPageRoute(
builder: (context) {
// TODO
}
);
}
},
debugShowCheckedModeBanner: false
);
}
}
tl;dr
//in your example: settings.name = "/post?id=123"
final settingsUri = Uri.parse(settings.name);
//settingsUri.queryParameters is a map of all the query keys and values
final postID = settingsUri.queryParameters['id'];
print(postID); //will print "123"
Drilldown
In a perfect world you would access queryParameters with Uri.base.queryParameters because:
Uri.base
Returns the natural base URI for the current platform.
When running in a browser this is the current URL of the current page (from window.location.href).
When not running in a browser this is the file URI referencing the current working directory.
But currently there is an issue in flutter where you have #/ in your path which messes the Uri.base interpretation of the Uri.
Follow the issue #33245 until this matter is addressed and you will be able to use Uri.base.queryParameters
please follow this link further information https://flutter.dev/docs/cookbook/navigation/navigate-with-arguments
on your MaterialApp
onGenerateRoute: (settings) {
// If you push the PassArguments route
if (settings.name == PassArgumentsScreen.routeName) {
// Cast the arguments to the correct type: ScreenArguments.
final ScreenArguments args = settings.arguments;
// Then, extract the required data from the arguments and
// pass the data to the correct screen.
return MaterialPageRoute(
builder: (context) {
return PassArgumentsScreen(
title: args.title,
message: args.message,
);
},
or you can nativate like web using this plugin fluro
This is how I did it. You can edit it as per your requirements. If you want to use ?q= then use the split by or regex accordingly
Here is the example of both passing in argument as well as passing in url as /topic/:id
Route<dynamic> generateRoute(RouteSettings settings) {
List<String> pathComponents = settings.name.split('/');
final Map<String, dynamic> arguments = settings.arguments;
switch ("/"+pathComponents[1]) {
case shareTopicView:
return MaterialPageRoute(
builder: (context) => TopicPageLayout(topicID: pathComponents[2]));
case internalTopicView:
return MaterialPageRoute(
builder: (context) => TopicPageLayout(topicID: arguments['topicID']));
default:
return MaterialPageRoute(builder: (context) => LandingPage());
}
}
I'm new to Flutter, and I found a quirky workaround,...
import 'dart:html';
String itemID;
//My url looks like this,... http://localhost:57887/#item_screen/12345
//Counted 13 characters for '#item_screen/' then got the substring as below
itemID = window.location.hash.substring(13);
print(itemID) //12345
Not very sophisticated, but worked :-D
Add flutter_modular to your flutter web project.
current version: flutter_modular: ^3.1.1
Read dynamic routes section in: https://pub.dev/packages/flutter_modular#dynamic-routes
Example for the URL /post?id=123
Create your main widget with a MaterialApp and call the ´´´MaterialApp().modular()´´´ method.
// app_widget.dart
import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart';
class AppWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: "/",
).modular();
}
}
Create your project module file extending Module:
// app_module.dart
class AppModule extends Module {
#override
final List<Bind> binds = [];
#override
final List<ModularRoute> routes = [
ChildRoute('/', child: (_, __) => HomePage()),
ChildRoute('/post', child: (_, args) => PostPage(id: args.queryParams['id'])),
];
}
3.In main.dart file, wrap the main module in ModularApp to initialize it with Modular:
// main.dart
import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'app/app_module.dart';
void main() => runApp(ModularApp(module: AppModule(), child: AppWidget()));
And here is another way to do it:
My url pattern: www.app.com/#/xLZppqzSiSxaFu4PB7Ui
onGenerateRoute: (settings) {
List<String> pathComponents = settings.name.split('/');
if (pathComponents[1] == 'invoice') {
return MaterialPageRoute(
builder: (context) {
return Invoice(arguments: pathComponents.last);
},
);
} else
return MaterialPageRoute(
builder: (context) {
return LandingPage();
},
);
;
},
Here's a workaround that uses the 'default' route as my main route.
I did this because it seems to be the only way that Flutter will allow me to open a URL with an ID in it, that doesn't return a 404.
E.g. Flutter does not seem to respect the '?' separator. So a URL with an ID in it, is read by flutter as an unknown URL. E.g. site.com/invoice?id=999 will return a 404, even in /invoice is set up as route.
My goal: I have a 1-page web app that simply displays a single invoice at a time, which corresponds to the ID in the URL.
My URL
app.com/#/xLZppqzSiSxaFu4PB7Ui
The number at the end of the URL is a FireStore Doc ID.
Here's the code in MyApp:
onGenerateRoute: (settings) {
List<String> pathComponents = settings.name.split('/');
switch (settings.name) {
case '/':
return MaterialPageRoute(
builder: (context) => Invoice(),
);
break;
default:
return MaterialPageRoute(
builder: (context) => Invoice(
arguments: pathComponents.last,
),
);
}
},
This sends 'xLZppqzSiSxaFu4PB7Ui' to the 'Invoice' widget.
Try onGenerateRoute with below sample
final info = settings.arguments as Mediainfo?;
settings = settings.copyWith(
name: settings.name! + "?info=" + info!.name, arguments: info);
return MaterialPageRoute(
builder: (_) => MediaDetails(info: info), settings: settings);
This was my solution:
First, kind of seperate, I have an abstract class, AppRoutes, which is just a collection of string-routes, that way they're easily maintainable and switchable.
abstract class AppRoutes {
static const String guestGetMember = "/guest_getMember";
...
static render(String url, {Map<String, dynamic>? params}) {
return Uri(path: url, queryParameters: params ?? {}).toString();
}
}
Now for the code:
Route<dynamic> generateRoute(RouteSettings settings) {
Uri uri = Uri.parse(settings.name ?? "");
Map<String, dynamic> params = {};
// Convert numeric values to numbers. This is optional.
// You can instead `int.parse` where needed.
uri.queryParameters.forEach((key, value) {
params[key] = int.tryParse(value) ?? value;
});
final Map<dynamic, dynamic> arguments = (settings.arguments ?? {}) as Map<dynamic, dynamic>;
return MaterialPageRoute(builder: (context) {
switch (uri.path) {
case AppRoutes.guestGetMember:
return CardGuestViewProfile(memberID: params['memberID']!);
case AppRoutes...:
return...;
default:
return AppScreen();
}
// Navigator routes update web URLs by default,
// while `onGeneratedRoute` does not. That last
// line forces it to. The whole of using url
// variables for me was so that certainly URLs
// were easily copiable for sharing.
}, settings: (RouteSettings(name: settings.name)));
}
And then I call it with
Navigator.pushNamed(context,
AppRoutes.render(AppRoutes.guestGetMember,
params: {'memberID': memberID.toString()}),
arguments: {}));
params will be easily visible to web-users because it's a URL variable, while arguments will not be. This of course doesn't mean that arguments is by any means secure, it just means that non-essential information can be passed through this.

Navigator pass arguments with pushNamed

Might have been asked before but I can't find it but how do you pass a arguments to a named route?
This is how I build my routes
Widget build(BuildContext context) {
return new Navigator(
initialRoute: 'home/chooseroom',
onGenerateRoute: (RouteSettings settings) {
WidgetBuilder builder;
switch (settings.name) {
case 'home/chooseroom':
// navigates to 'signup/choose_credentials'.
builder = (BuildContext _) => new ChoosePage();
break;
case 'home/createpage':
builder = (BuildContext _) => new CreateRoomPage();
break;
case 'home/presentation':
builder = (BuildContext _) => new Presentation();
break;
default:
throw new Exception('Invalid route: ${settings.name}');
}
return new MaterialPageRoute(builder: builder, settings: settings);
},
);
This is how you call it
Navigator.of(context).pushNamed('home/presentation')
But what if my widget is new Presentation(arg1, arg2, arg3)?
No need for onGenerateRoute. Simply use
var exampleArgument = 'example string';
Navigator.pushNamed(
context,
'/otherscreen',
arguments: {'exampleArgument': exampleArgument},
);
and extract the arguments as follows:
#override
Widget build(BuildContext context) {
final arguments = (ModalRoute.of(context)?.settings.arguments ?? <String, dynamic>{}) as Map;
print(arguments['exampleArgument']);
return Scaffold(...);
}
pushNamed() now supports arguments as of this merged pull request. If you can't wait, switch to channel master (flutter channel master and probably followed by flutter upgrade).
How to send:
Navigator.pushNamed(ctx, '/foo', arguments: someObject);
How to receive:
...
return MaterialApp(
...
onGenerateRoute: _getRoute,
...
);
...
Route<dynamic> _getRoute(RouteSettings settings) {
if (settings.name == '/foo') {
// FooRoute constructor expects SomeObject
return _buildRoute(settings, new FooRoute(settings.arguments));
}
return null;
}
MaterialPageRoute _buildRoute(RouteSettings settings, Widget builder) {
return new MaterialPageRoute(
settings: settings,
builder: (ctx) => builder,
);
}
The "arguments" can be any object, e.g. a map.
It took me a while to notice this, as I'm a newbie to Flutter. But the arguments you add using Navigator.pushNamed get sent directly to the widget you pushed NOT the MaterialApp for routing.
So in widget you push a new screen from you'll have:
Navigator.pushNamed(
context,
SomePage.routeName,
arguments: {
'v1': 'data1',
'v2': 'data2',
'v3': 'data3',
},
)
You won't need those arguments in your constructor at all. Instead your pull them out in the SomePage widget like the others are saying; namely via:
final arg = ModalRoute.of(context)!.settings.arguments as Map;
and can assign them within SomePage build like:
randomVar1 = arg['v1'];
randomVar2 = arg['v2'];
randomVar3 = arg['v3'];
using whatever keys you put in.
if you want MaterialApp to handle it then you use the onGenerateRoute method. This took me forever to notice that the arguments go directly to the widget pushed. For me it was counter-intuitive.
Arguments can be any object, you can make an array as you can see:
Navigator.of(context).pushNamed('/upload', arguments: {'_imagePath':_imagePath,
'num_docfiscal':num_docfiscal,'dta_docfiscal':dta_docfiscal});
and access to the router class.
Basically you have 2 options :
Use some 3rd party package for routing - I think the best is Fluro .
exploit onGenerateRoute . This option is limited to args you can pass (string/numbers)
To use second option, assuming you want to pass three arguments: Navigator.of(context).pushNamed('home/presentation:arg1:1337:hello')
MaterialApp (
... ,
onGenerateRoute: handleRoute,
routes:... , )
Route<dynamic> handleRoute(RouteSettings settings) {
WidgetBuilder builder;
final List<String> uri = settings.name.split('/');
if (uri[0].startsWith('home')) {
// handle all home routes:
if(uri[1].startsWith('presentation:'){
// cut slice by slice
final String allArgs =
uri[1].substring('presentation:'.length);
final List<String> args = allArgs.split(':');
// use your string args
print(args[0]); // prints "arg1"
int x = int.parse(args[1]); // becomes 1337
print(args[2]); // prints "hello"
builder = (ctx)=> Presentation(args[0],args[1],args[2]);
...
For simple navigation with arguments from WidgetA to WidgetB
Define routes in MaterialApp main widget:
return MaterialApp(
routes: {
'/routeAB': (context) => WidgetB(),
},
In WidgetA use pushNamed method to navigate to WidgetB:
onTap: () {
Navigator.pushNamed(context, '/routeAB',
arguments: {
'arg1': val1,
'arg2': val2,
...
Get arguments in WidgetB:
Map args = ModalRoute.of(context).settings.arguments;
Note: new routing options may be available soon.
Could this be a possible option?
Use push with a RouteSettings argument specifying the named route.
This way you can directly pass arguments of any type (including objects) to your destination Widget in a type safe manner and skip using arguments. You won't need to create a single-use throwaway arguments class nor a Map.
RouteSettings in push can supply a named Route to your Navigation stack which you can search for / use in future routing decisions, just the same as if you had used pushNamed.
Push + RouteSettings
To use push with a named route, use RouteSettings argument with the route name.
Example: a user logs in on Page1 and now you want push them from Page1 to Page2
Directly inside Page1 pass the User object (loggedInUser) to Page2 within a Navigator.push call and use a RouteSettings arg with your route name (/page2).
Navigator.push(context, MaterialPageRoute(
builder: (context) => Page2(user: loggedInUser),
settings: RouteSettings(name: '/page2')
));
And in Page2 widget, you can expect and use the User object directly.
class Page2 extends StatefulWidget {
final User loggedInUser;
Page2(this.loggedInUser);
#override
_Page2State createState() => _Page2State();
}
class _Page2State extends State<Page2> {
User loggedInUser;
#override
void initState() {
super.initState();
loggedInUser = widget.loggedInUser;
print(loggedInUser.name);
}
#override
Widget build(BuildContext context) {
}
}
Later on you can use /page2 route name. For example if you're at /page3 and you want to popUntil(context, ModalRoute.withName('/page2')), this allows that.
Under the Hood
Flutter's Navigator class shows pushNamed uses push + routeNamed and routeNamed uses RouteSettings.
Future<T> pushNamed<T extends Object>(
String routeName, {
Object arguments,
}) {
return push<T>(_routeNamed<T>(routeName, arguments: arguments));
}
Route<T> _routeNamed<T>(String name, { #required Object arguments, bool allowNull = false }) {
final RouteSettings settings = RouteSettings(
name: name,
arguments: arguments,
);
Route<T> route = widget.onGenerateRoute(settings) as Route<T>;
return route;
Pass arguments:
Navigator.pushNamed(YourScreen.routeName, arguments: {"title":myTitle, "user_name":userName});
Extract arguments:
Map<String, dynamic> arguments = new Map<String, dynamic>.from(settings.arguments);
page = MyRecordingScreen(title: arguments["title"], tags: arguments["user_name"], );
For named Routes with multiple arguments or dynamic object you need to do as follow(this is MVVM pattern example):
navigator:
void navigateEditParty(int index) {
_navigationService.navigateTo(PartyEditRoute,
arguments: {"hunter": hunter, "index": index});
}
router:
case PartyEditRoute:
Map args = settings.arguments;
return _getPageRoute(
routeName: settings.name,
viewToShow: PartyEditView(
hunter: args["hunter"],
index: args["index"],
),
);
class:
PartyEditView({Key key, this.hunter, this.index}) : super(key: key);
It is always best to wrap your arguments in an object to avoid error prone code.
Below are the working example on how you can achieve it in flutter dart.
To send
Navigator.of(context).pushNamed(Routes.submitSuccess, arguments: successModel);
To Receive
case Routes.submitSuccess:
if (settings.arguments is SubmitSuccessModel) {//Check for instance here
return CupertinoPageRoute(
builder: (_) =>
SubmitSuccessPage(successModel: settings.arguments));
}
Model Object
class SubmitSuccessModel {
SubmitSuccessModel(
{this.title, this.desc, this.actionButtonName, this.widget});
String title;
String desc;
String actionButtonName;
Widget widget;
}
final SubmitSuccessModel successModel = SubmitSuccessModel(
title: 'Title',
desc: 'Desc',
actionButtonName: 'Done',
);
It took me a while to notice this, as I'm a newbie to Flutter. But the arguments you add using Navigator.pushNamed get sent directly to the widget you pushed NOT the MaterialApp for routing.
You don't need those arguments in your constructor at all. Instead your pull them out in the widget like the others are saying; namely via:
final arg = ModalRoute.of(context)!.settings.arguments as Map;
and can assign them like:
randomVar1 = arg['v1'];
randomVar2 = arg['v2'];
randomVar3 = arg['v3'];
using whatever keys you put in.
if you want MaterialApp to handle it then you use the onGenerateRoute method. This took me forever to notice that the arguments go directly to the widget pushed. For me it was counter-intuitive.
screeen_1.dart
Navigator.of(context).pushNamed('/screen2',arguments: {'var1': val1 ,'var2': val2, "var3": val3 ,"var4" : val4}); //sending of the values to route_generator.dart
route_generator.dart
class RouteGenerator {
static Route<dynamic> generateRoute(RouteSettings settings) //from screeen_1.dart
{
// Getting arguments passed in while calling Navigator.pushNamed
Map args = settings.arguments;
switch (settings.name) {
case '/screen2':
if (args is Map) {
return MaterialPageRoute(
builder: (_) => screen_2(args),
);
}
return _errorRoute(); //optional written in a scaffold when any error arrises
default:
return _errorRoute(); //optional written in a scaffold when any error arrises
}
screen_2.dart
screen_2(Map args) //follow the params from the route_generator.dart
{
return Scaffold(
body: FutureBuilder(
future: _screen_2EndpointProvider.fetchScreen_2(args['var1'], args["var2"], args['var3'], args['var4']), //API call inside which it called the args
builder: (context, snapshot) {
return snapshot.hasData
? screen_2(param: snapshot.data)
: Center(child: CircularProgressIndicator());
}),
);
}
I tried to provide a simple example, if it could be helpful to anyone, because i was facing the same error after a lots of trials it got solved.
After a long day fighting with the routes and onGeneratedRoutes on how i should pass the data and retrieve i finally understand it here is what i learned about
routes: - the property of MaterialApp
routes: {'/profile': (context) => const Profile()}
onTap () {
Navigator.pushNamed(context, '/profile', arguments: {'name': 'someone'});
};
On the profile Screen
final args = ModalRoute.of(context)!.settings.arguments as Map;
don't forget do Downcast to the right data type using the as keyword.
useGeneratedRoute: - the property of MaterialApp for dynamic routes
onGenerateRoute: Routes.RouteGenerator,
on Routes class
static Route<dynamic> generateR(RouteSettings settings) {
switch (settings.name) {
final args = settings.arguments;
case 'profile':
if (args is Map) {
return MaterialPageRoute(
builder: (context) => Profile(
title: args,
));
}
default:
return MaterialPageRoute(
builder: ((context) => const NotFound()));
}
}
on class Profile create a constructor and a field of type Map or whatever you what to pass as data.
final Map<String> data;
const Profile({
super.key,
required this.data,
});
and you can now use the data as you wish.
child: const Text(data[name]);
So use the best option for your context i struggle so much to understand because i was get the null error. hope it's help you need
That's how to send parameters / arguments-
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NewPresentation(
id: '101',
name: 'Unnamed',
)));
},
child: const Text('Submit'))
And that's how to receive -
class NewPresentation extends StatefulWidget {
final String id, name;
const NewPresentation({Key? key, required this.id, required this.name}) : super(key: key);
#override
State<NewPresentation> createState() => _NewPresentationState();
}
class _NewPresentationState extends State<NewPresentation> {
Widget build(BuildContext context) {
return Text(widget.id + widget.name);
}
}
Screen 1 :
Navigator.push(
context,
MaterialPageRoute(
settings: RouteSettings(arguments: e),
builder: (context) =>
const AnnouncementDetail()),
);
screen 2 :
//get parameters
final arg = ModalRoute.of(context)!.settings.arguments as Announcement;
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Anang', style: TextStyle(fontWeight: FontWeight.bold),),
Text('Diposting pada ${DateFormat("d MMMM yyyy", "id_ID")
.format(DateTime.parse('${arg.announcementDate!} 00:00:00.000'))}', style: const TextStyle(fontWeight: FontWeight.normal)),
const SizedBox(height: 16),
Text(arg.announcementTitle!, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 20),),
const SizedBox(height: 16),
Html(
style : {
"body": Style(
padding: EdgeInsets.zero,
margin: Margins(
bottom: Margin.zero(),
left: Margin.zero(),
top: Margin.zero(),
right: Margin.zero(),
)
)
},
data: arg.announcement)
],
)
Announcement.dart
class Announcement {
Announcement({
this.id,
this.announcementDate,
this.announcementTitle,
this.announcement,
this.month,
this.createdBy,
this.createdAt,
this.updatedAt,});
Announcement.fromJson(dynamic json) {
id = json['id'];
announcementDate = json['announcement_date'];
announcementTitle = json['announcement_title'];
announcement = json['announcement'];
month = json['month'];
createdBy = json['created_by'];
createdAt = json['created_at'];
updatedAt = json['updated_at'];
}
int? id;
String? announcementDate;
String? announcementTitle;
String? announcement;
String? month;
int? createdBy;
String? createdAt;
String? updatedAt;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['id'] = id;
map['announcement_date'] = announcementDate;
map['announcement_title'] = announcementTitle;
map['announcement'] = announcement;
map['month'] = month;
map['created_by'] = createdBy;
map['created_at'] = createdAt;
map['updated_at'] = updatedAt;
return map;
}
}
// Main Screen from where we want to Navigate
Navigator.pushNamed(
context,
"/ScreenName",
arguments: {
'id': "20"
});
I also faced the same issue I was using NavigationRoutes
class NavigationRoutes {
static Route<dynamic> generateScreenRoute(RouteSettings settings) {
// TODO below we are able to get Arguments list
Map args = settings.arguments;
switch (settings.name) {
case "Screen Name":
return MaterialPageRoute(builder: (context) => ScreenName(args));
// TODO pass above argument here and get in the form of Constructor in Screen
// Name Class
}
}