I have 2 pages, page1 andpage2. I want to validate that when the app is opened and there is no token or it is false, it redirects to page1 otherwise it redirects to page2, and when I have more pages I want that if there is a valid token, continues the normal flow of the navigation, I was trying this and I have this problem:
in the gif the token is not defined, the validation apparently does well, but the problem is that it continues to reload the current view, I am looking for something more optimal that avoids loading a route if some condition is not met
how can I solve that?
Map<String, WidgetBuilder> getRoutes() {
return <String, WidgetBuilder>{
'/': (BuildContext context) =>
checkNavigation("/", pag1(), context),
'page1': (BuildContext context) =>
checkNavigation("page1", page1(), context),
'page2': (BuildContext context) =>
checkNavigation("/page2", page2(), context)
};
}
dynamic checkNavigation(
String page, dynamic pageContext, BuildContext context) {
if (storage.token && page == "/") {
//Navigator.pushNamedAndRemoveUntil(context, 'page2', (_) => false);
return page2();
} else if (storage.token == false) {
//Navigator.pushNamedAndRemoveUntil(context, 'page1', (_) => false);
return page1();
} else {
return pageContext;
}
}
in my main:
.
.
.
MaterialApp(
title: 'route validation',
initialRoute: '/',
routes: getRoutes(),
It's better to control this behavior in your own abstractions and change routes only if necessary.
I would recommend to add some splash screen at root route and navigate to appropriate route, once token is initialized.
Future<void> asyncInit() {/*...*/}
void initState() {
/* ... */
asyncInit().then((_) => /* push appropriate first route */);
}
Map<String, WidgetBuilder> getRoutes() {
return <String, WidgetBuilder>{
'/': (BuildContext context) => SplashScreen(),
/* other routes */
};
}
If you need intercept other navigation events you can add your own proxy class, this may be easily implemented using Provider package
class MyNavigator {
final GlobalKey<NavigatorState> navigatorKey;
MyNavigator(this.navigatorKey);
static MyNavigator of(BuildContext context) => context.read<MyNavigator>();
Future<T> pushNamed<T extends Object>(
BuildContext context,
String routeName, {
Object arguments,
}) {
// add any additional logic and conditions here
return navigatorKey.currentState.pushNamed<T>(routeName, arguments: arguments);
}
// add any other methods you need
}
// somewhere at the top of widget tree above Widgets/Material/CupertinoApp widget.
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
// add provider with navigator key above navigator
Provider(
create: (_) => MyNavigator(navigatorKey),
child: MaterialApp(navigatorKey: navigatorKey, /*...*/)
)
// use it
MyNavigator.of(context).pushNamed(...)
There is much work going right now to implement Navigator 2.0 with Router and Pages API, which gives you more control and flexibility on routing.
tracking issue:
https://github.com/flutter/flutter/issues/45938
design docs:
https://flutter.dev/go/navigator-with-router
https://flutter.dev/go/router-and-widgetsapp-integration
Pages API is already available in current stable release, but there is not enough documentation and examples at the moment.
Related
I'm integrating GoRouter in my Flutter app where I'm already using Riverpod. I have an isAuthorizedProvider defined as follows:
final isAuthorizedProvider = Provider<bool>((ref) {
final authStateChanged = ref.watch(_authStateChangedProvider);
final user = authStateChanged.asData?.value;
return user != null;
});
And I'm not sure how to define a GoRouter that depends on the Provider above. I've come up with the following:
final goRouterProvider = Provider<GoRouter>((ref) => GoRouter(
debugLogDiagnostics: true,
redirect: (state) {
final isAuthorized = ref.watch(isAuthorizedProvider);
final isSigningIn = state.subloc == state.namedLocation('sign_in');
if (!isAuthorized) {
return isSigningIn ? null : state.namedLocation('sign_in');
}
// if the user is logged in but still on the login page, send them to
// the home page
if (isSigningIn) return '/';
// no need to redirect at all
return null;
},
routes: [
GoRoute(
path: '/',
...,
),
GoRoute(
name: 'sign_in',
path: '/sign_in',
...,
),
GoRoute(
name: 'main',
path: '/main',
...,
),
...
],
));
class MyApp extends ConsumerWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
final goRouter = ref.watch(goRouterProvider);
return MaterialApp.router(
routeInformationParser: goRouter.routeInformationParser,
routerDelegate: goRouter.routerDelegate,
);
}
Is this the right way to do it?
I don't thing you should be calling this line
ref.watch(isAuthorizedProvider);
inside the redirect block, because that will cause your entire GoRouter instance to rebuild (and you'll lose the entire nav stack).
This is how I've done it:
class AppRouterListenable extends ChangeNotifier {
AppRouterListenable({required this.authRepository}) {
_authStateSubscription =
authRepository.authStateChanges().listen((appUser) {
_isLoggedIn = appUser != null;
notifyListeners();
});
}
final AuthRepository authRepository;
late final StreamSubscription<AppUser?> _authStateSubscription;
var _isLoggedIn = false;
bool get isLoggedIn => _isLoggedIn;
#override
void dispose() {
_authStateSubscription.cancel();
super.dispose();
}
}
final appRouterListenableProvider =
ChangeNotifierProvider<AppRouterListenable>((ref) {
final authRepository = ref.watch(authRepositoryProvider);
return AppRouterListenable(authRepository: authRepository);
});
final goRouterProvider = Provider<GoRouter>((ref) {
final authRepository = ref.watch(authRepositoryProvider);
final appRouterListenable =
AppRouterListenable(authRepository: authRepository);
return GoRouter(
debugLogDiagnostics: false,
initialLocation: '/',
redirect: (state) {
if (appRouterListenable.isLoggedIn) {
// on login complete, redirect to home
if (state.location == '/signIn') {
return '/';
}
} else {
// on logout complete, redirect to home
if (state.location == '/account') {
return '/';
}
// TODO: Only allow admin pages if user is admin (#125)
if (state.location.startsWith('/admin') ||
state.location.startsWith('/orders')) {
return '/';
}
}
// disallow card payment screen if not on web
if (!kIsWeb) {
if (state.location == '/cart/checkout/card') {
return '/cart/checkout';
}
}
return null;
},
routes: [],
);
}
Note that this code is not reactive in the sense that it will refresh the router when the authState changes. So in combination with this, you need to perform an explicit navigation event when you sign-in/sign-out.
Alternatively, you can use the refreshListenable argument.
You can do it this way using redirect, however I've come up with a way that uses navigatorBuilder. This way you maintain the original navigator state (you will be redirected back to whichever page you originally visited on web or with deep linking), and the whole router doesn't have to be constantly be rebuilt.
final routerProvider = Provider((ref) {
return GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const OrdersScreen(),
),
GoRoute(
path: '/login',
builder: (context, state) => const AuthScreen(),
),
],
navigatorBuilder: (context, state, child) {
return Consumer(
builder: (_, ref, __) =>
ref.watch(authControllerProvider).asData?.value == null
? Navigator(
onGenerateRoute: (settings) => MaterialPageRoute(
builder: (context) => AuthScreen(),
),
)
: child,
);
},
);
});
navigatorBuilder basically allows you to inject some widget between the MaterialApp and the Navigator. We use Riverpod's consumer widget to access the ref and then the whole router doesn't have to be rebuilt, and we can access auth state using the ref.
In my example, ref.watch(authControllerProvider) returns an AsyncValue<AuthUser?>, so if the user is logged in, we return the child (current navigated route), if they are logged out, show them login screen, and if they are loading we can show a loading screen etc.
If you want to redirect users based on roles (e.g. only admin can see admin dashboard), then that logic should go into the redirect function using a listenable as #bizz84 described.
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}')),
));
}
}
}
}
}
I have a MaterialApp(navigatorKey: rootNavigatorKey ) , which has two routes
'/datoPhotoViewer' without a drawer
'/' which has a Drawer, this drawer has listItems that pushes the widgets on his child : Navigator(navigatorKey: navigatorKey ),
The problem is when i press the back button, whole app closes, My idea was then, catch Back-Button-pressed , and calling the navigatorKey.currentState.pop() from the adequate navigatorKey
So to do it, i need WillPopScope .. when I set a WillPopScope on each route, i find that when i press Back button, the app was still closing
So i've decided to delete all the WillPopScope i had, and put only one WillPopScope(onWillPop: onPop) on main.dart where by trying, i find that it catches every 'back button pressed', and changing that static function onPop when i open a route with :
setOnPopFunction(Function func){
onPop = func;
}
But when i call this function, onPop function isn't changed ,
Another thing i saw happening : I'm getting logged those weird results when accessing the navigatorKeys
final GlobalKey<NavigatorState> rootNavigatorKey = GlobalKey();
final GlobalKey<NavigatorState> navigatorKey = GlobalKey();
Function onPop ;
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
onPop = (){
log("___Navigator1 context "+ ModalRoute.of(rootNavigatorKey.currentContext).toString());
//this prints '___Navigator1 context null' , even though i can push /datoPhotoViewer to Navigator 1
log("___Navigator2 routeName "+ ModalRoute.of(navigatorKey.currentContext).settings.name.toString());
//always printing '___Navigator2 routeName /' ,even when this navigatorKey has been pushed to another route
return Future.value(false);
};
return MaterialApp(
navigatorKey: rootNavigatorKey, //Navigator 1 key
routes: {
'/': (context) { //Navigator 1 route
return WillPopScope(
onWillPop: onPop //this is the only onWillPop function i can get called when pressing back button , Which i'll change with setOnPopFunction()
child: MyMainStructure( //MyMainStructure is a Scaffold with the drawer
child: Navigator(
key: navigatorKey, //Navigator 2 key
onGenerateRoute: (RouteSettings settings) {
switch (settings.name) {
case '/': //Navigator 2 route
return MaterialPageRoute(
builder: (_) => TodosLosItemsPage());
case '/categoria': //Navigator 2 route
return MaterialPageRoute(
builder: (_) =>
CategoriaConceptosPage(settings.arguments),
);
}
},
initialRoute: '/', //Navigator 2 initial
)),
);
},
'/datoPhotoViewer': (context) => //Navigator 1 route
datoPhotoViewer(ModalRoute.of(context).settings.arguments),
},
initialRoute: '/', //Navigator 1 initial
),
);
}
}
setOnPopFunction(Function func){
onPop = func;
}
thanks in advance
To solve this i ended up using an external package
https://pub.dev/packages/back_button_interceptor
I've disabled the back button for all the app with a
WillPopScope(
onWillPop: () async => false,
child: ....
And making a BackButtonManager class to override the Back Button
List<Function> _listeners = List<Function>();
class BackButtonManager {
static void addCustomFunction(Function func){
_listeners.add(func);
BackButtonInterceptor.add(func);
}
static void resetListeners(){
_listeners.forEach((element) {
BackButtonInterceptor.remove(element);
});
_listeners.clear();
}
This is the structure of what i do when a new route is opened, on initState i call:
BackButtonManager.resetListeners();
BackButtonManager.addCustomFunction ((bool asd){
//handle it
//for example: navigatorKey.CurrentState.pop();
// then check which widget is going to be onscreen now ,
// and when this BackButton is pressed , add to BackButtonManager it's
// BackButtonFunction
BackButtonManager.resetListeners();
BackButtonManager.addCustomFunction ((bool asd){
//handle the incoming OnScreenWidget
return false;
});
return false;
});
I'd recommend to make a list that represents your stacks of routes, there might be another way , but this works
Rather than pushing the user around with Navigator.push when they sign in or out, I've been using a stream to listen for sign in and sign out events.
StreamProvider<FirebaseUser>.value(
value: FirebaseAuth.instance.onAuthStateChanged,
)
It works great for the home route as it handles logging in users immediately if they're still authed.
Consumer<FirebaseUser>(
builder: (_, user, __) {
final isLoggedIn = user != null;
return MaterialApp(
home: isLoggedIn ? HomePage() : AuthPage(),
// ...
);
},
);
However, that's just for the home route. For example, if the user then navigates to a settings page where they click a button to sign out, there's no programmatic logging out and kicking to the auth screen again. I either have to say Navigator.of(context).pushNamedAndRemoveUntil('/auth', (_) => false) or get an error about user being null.
This makes sense. I'm just looking for possibly another way that when they do get logged out I don't have to do any stack management myself.
I got close by adding the builder property to the MaterialApp
builder: (_, widget) {
return isLoggedIn ? widget : AuthPage();
},
This successfully moved me to the auth page after I was unauthenticated but as it turns out, widget is actually the Navigator. And that means when I went back to AuthPage I couldn't call anything that relied on a parent Navigator.
What about this,you wrap all your screens that depend on this stream with this widget which hides from you the logic of listening to the stream and updating accordingly(you should provide the stream as you did in your question):
class AuthDependentWidget extends StatelessWidget {
final Widget childWidget;
const AuthDependentWidget({Key key, #required this.childWidget})
: super(key: key);
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {//you handle other cases...
if (snapshot.currentUser() != null) return childWidget();
} else {
return AuthScreen();
}
},
);
}
}
And then you can use it when pushing from other pages as follows:
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (ctx) => AuthDependentWidget(
childWidget: SettingsScreen(),//or any other screen that should listen to the stream
)));
I found a way to accomplish this (LoVe's great answer is still completely valid) in case anyone else steps on this issue:
You'll need to take advantage of nested navigators. The Root will be the inner navigator and the outer navigator is created by MaterialApp:
return MaterialApp(
home: isLoggedIn ? Root() : AuthPage(),
routes: {
Root.routeName: (_) => Root(),
AuthPage.routeName: (_) => AuthPage(),
},
);
Your Root will hold the navigation for an authed user
class Root extends StatefulWidget {
static const String routeName = '/root';
#override
_RootState createState() => _RootState();
}
class _RootState extends State<Root> {
final _appNavigatorKey = GlobalKey<NavigatorState>();
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
final canPop = _appNavigatorKey.currentState.canPop();
if (canPop) {
await _appNavigatorKey.currentState.maybePop();
}
return !canPop;
},
child: Navigator(
initialRoute: HomePage.routeName,
onGenerateRoute: (RouteSettings routeSettings) {
return MaterialPageRoute(builder: (_) {
switch (routeSettings.name) {
case HomePage.routeName:
return HomePage();
case AboutPage.routeName:
return AboutPage();
case TermsOfUsePage.routeName:
return TermsOfUsePage();
case SettingsPage.routeName:
return SettingsPage();
case EditorPage.routeName:
return EditorPage();
default:
throw 'Unknown route ${routeSettings.name}';
}
});
},
),
);
}
}
Now you can unauthenticate (FirebaseAuth.instance.signout()) inside of the settings page (or any other page) and immediately get kicked out to the auth page without calling a Navigator method.
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.