I don't use named routes in the app. Instead followed by the inspiration from FlutterBloc library I use route definitions like this.
class OrganizationPage extends StatelessWidget {
static Route route() {
return MaterialPageRoute<void>(builder: (_) => OrganizationPage());
}
// ....
}
And I navigate using...
Navigator.push(
context,
LoginPage.route(),
);
Assume from the point of loading the OrganizationPage I have navigated few times. I want a button press that will pop OrganizationPage route + every thing else on top of it and load a new page. How can this be done?
I thought of doing some thing like this...
Navigator.pushAndRemoveUntil(
context,
HomePage.route(),
(Route<dynamic> route) {
// print("Checking route: $route");
// return route.builder.toString().contains("OrganizationPage")
// `builder` is not available, even though it did in the debugger.
},
);
Can what I want be done? If so how?
I assume if I use named routes it might be easier? Unfortunately I have written a lot of code and prefer to avoid a refactor. Also this way I can accept any args to a page easily.
Thanks in advance.
Tried (Route<dynamic> route) => route.settings.name == "/OrganizationPage" ?
Update
Using the method highlighted in the question doesn't result a route name. But a route name can be provided explicitly.
static Route route() {
return MaterialPageRoute<void>(
builder: (_) => OrganizationPage(),
settings: RouteSettings(name: '/OrganizationPage'),
);
}
Now that the route has a name, we can use
Navigator.pushAndRemoveUntil(
context,
HomePage.route(),
(Route<dynamic> route) => route.settings.name == OrganzationPage.route().settings.name;
);
Related
I have been wrestling with this issue for a couple of days now and I haven't been able to find a solution, hence the post.
At one point in my app, I need to pop 2 routes from the navigator stack and push a new route. After researching the best way to do this, I have found that using pushNamedAndRemoveUntil is the best way, as I can specify ModalRoute.withName('/<route_name>') and it will pop the routes until it reaches /<route_name> at which point it will stop and push the new route. This is the line I have been using Navigator.of(context).pushNamedAndRemoveUntil('/raceadmin_reporting', ModalRoute.withName('/raceadmin_page'));.
My issue though is that it doesn't work for me. It doesn't seem to matter what I put in /<route_name>, pushNamedAndRemoveUntil pops all the routes, which leads me to believe that ModalRoute.withName never returns true.
I have also tried Navigator.of(context).pushNamedAndRemoveUntil('/raceadmin_reporting', (route) => route == RaceAdminPage.route()); and it doesn't work either.
When I look at the debugger, this is what I see:
App Navigator Stack
The route I am trying to pop until is the RaceAdmin page, which is clearly in the stack. In the definition of that class, I added the line static const routeName = '/raceadmin_page'; which is what I call in ModalRoute.withName('/raceadmin_name') and it doesn't work.
My routes are defined in the routes.dart file as per below:
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case '/':
return MaterialPageRoute(builder: (context) => RailMeatApp());
case '/raceadmin_page':
return MaterialPageRoute(builder: (context) => const RaceAdminPage());
case '/raceadmin_pendinglist':
return MaterialPageRoute(builder: (context) => PendingResultsList());
case '/raceadmin_pendingresults':
final args = settings.arguments as Map<String?, String?>;
return MaterialPageRoute(
builder: (context) => PendingResultsPage(
raceId: args['raceId'] as String,
));
case '/raceadmin_reporting':
return MaterialPageRoute(builder: (context) => RaceAdminReporting());
default:
return _errorRoute();
}
}
And my MaterialApp is defined as per below:
return MaterialApp(
navigatorKey: _navigatorKey,
home: _railmeatHome(),
onGenerateRoute: RailmeatRoutes.generateRoute,
);
}
In the Flutter debugger, I can look at the MaterialApp widget and I can see in its state that the navigator has 4 entries in its _history property as shown below:
MaterialApp Widget Properties
If I click on any of the entries in the _history, I see the the same info as below:
Route property in _history
As you can see, the name property under _settings is null, which, in my mind, would explain why ModalRoute.withName can't find the right route, but I am not sure that my thinking is accurate.
What should I do differently to make pushNamedAndRemoveUntil work?
Thanks a lot in advance,
Bertrand.
Try this instead
Navigator.of(context)
.pushNamedAndRemoveUntil('/routename', (Route<dynamic> route) => false);
Or
Navigator.of(context).popUntil(ModalRoute.withName('/root'));
I have not created any routes to navigate between screens. I use Navigator to navigate:
Navigator.push(context, MaterialPageRoute(builder: (context) => HomePage()));
what I have done is navigate to four screens from homePage to success screen:
HomePage => CreatePostScreen => CreateImagePost => SuccessfulScreen
when I reach to successfulscreen I would like to pop two screens and get back to CreatePostScreen.
I do not want to write Navigator.pop(context) two times.
I tried to use this, but it will come up with a black screen:
Navigator.popUntil(context, (route) => route is CreatePostScreen);
but this is not working. I would like to learn how flutter handles widget navigation not by route names and solution to this.
I know something about how navigator class handles with route name but I would like to know how to solve it if I push widgets and its working.
What you're trying to do :
Navigator.popUntil(context, (route) => route is CreatePostScreen);
Doesn't work because route is of type Route, not a widget. This leads to all the routes being popped since no Route satisfies your predicate.
What you should do is push your route with a setting, e.g. :
Navigator.push(context, MaterialPageRoute(builder: (context) => HomePage(), settings: RouteSettings(name: "/home")));
And then use that in your predicate. E.g. :
Navigator.popUntil(context, (route) => route.settings.name == "/home");
Hope this will help you. You can use popUntil method of Navigation Class.
int count = 0;
Navigator.of(context).popUntil((_) => count++ >= 2);
You would try with the below code:
onPressed: () async {int count = 0; Navigator.of(context).popUtil((_)=> count++>= 2);}
The code you would refer from is that, you would implement the logic to let the system indicate whether pop continues if it returns false it will keep popping until it the logic returns true
void popUntil(bool Function(Route<dynamic>) predicate)
If you want to pop two screens you can use cascade operator like this:
Navigator.of(context)..pop()..pop();
I could not understand why my screen build was being executed when popping. I narrowed it down to this line:
final String userId = ModalRoute.of(context).settings.arguments;
Why does the screen rebuild? How should I stop it rebuilding, or bail out when it happens?
Here is a complete example, with counter-example:
https://dartpad.dev/9f83473a923e39e9c4b07840bc4aded7
The problem seems to be know by now, but no actual fixes have actually been found somehow (or so it seems).
It is discussed here : https://github.com/flutter/flutter/issues/63364
As well as here : https://github.com/flutter/flutter/issues/35575
Generally, the accepted solutions seem to either :
Pass the arguments to the page directly through parameters of the page class (see 'vivek yadav' answers).
Retrieve the arguments in the initState() method and directly inside addPostFrameCallback() (see code below) rather than in the build() or didChangeDependencies() methods.
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
final args = ModalRoute.of(context).settings.arguments;
print(args);
});
}
However, I am still very interested to know if you found another solution or actual fix to this issue. Currently, I am using the 1st solution I listed above but I am not quite satisfied with it to be honest.
We pass a class object in MaterialPageRoute for eg
Navigator.of(context).push(
MaterialPageRoute(
builder: (ctx) => ScreenWithoutArg(),
),
);
Sometimes, we need to send data while routing, also in some cases class is selected dynamically.
In such a scenario, If we add data implementation in class constructor then it's not a good coding practice.
Rather than we can pass the same data in RouteSettings.
// suggested
Navigator.of(context).push(
MaterialPageRoute(
builder: (ctx) => ScreenWithArg(),
settings: RouteSettings(arguments: 'Arg'),
),
);
// not suggested (Prone to code changes)
Navigator.of(context).push(
MaterialPageRoute(
builder: (ctx) => ScreenWithArg(arg1: 'argdata',arg2:'agrdata2'),
settings: RouteSettings(arguments: 'Arg'),
),
);
Thanks, #Patrick O'Hara for highlighting this stunning feature of the dart.
I dont get complatly your question but if you want to delete or pop before navigator
Navigator.of(context).push(
MaterialPageRoute(
builder: (ctx) => ScreenWithArg(),
settings: RouteSettings(arguments: 'Arg'),
),
);
instead use this line code
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (ctx) => ScreenWithArg(),
settings: RouteSettings(arguments: 'Arg'),
),
);
I cannot understand the reason why someone should use named routes, with Navigator.pushNamed(), instead of the normal way with Navigator.push().
The tutorial page states that:
if we need to navigate to the same screen in many parts of our apps,
this can result in code duplication. In these cases, it can be handy
to define a “named route,” and use the named route for Navigation
Duplication
How will the duplication be generated when using simple routing and how it will can be eliminated with the use of named routes?
I fail to understand what is the difference of
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
from
Navigator.pushNamed(context, '/second');
in the context of duplication.
Consider you go with Navigator.push() in many widgets:
// inside widget A:
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
// inside widget B:
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
// inside widget C:
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
Now let say you need to change your App and the widget SecondRoute needs to receive a value on it's constructor. Now you have a problem, since you have multiple copies of the same code on several locations, you need to make sure you will update all of those copies, which can be tedious and error prone:
// inside widget A:
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute(
title: 'Title A',
)),
);
// inside widget B:
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute(
title: 'Title B',
)),
)),
);
// inside widget C:
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute(
title: 'Title A', // ERROR! Forgot to change the variable after a copy/paste
)),
)),
);
Now let's consider you go with named routes.
Firstly I would never recommend anyone to actually use the name directly for navigation, but instead use a static variable reference, this way if you need to change it in the future its way simpler and secure, as you can't forget to update it anywhere, like this:
class Routes {
static const String second = '/second';
}
Another way is to have a reference inside the route itself, a static const String inside SecondRoute, so we can use it as SecondRoute.routeName. It's a matter of personal preference IMO.
Then your widgets will navigate using:
// inside widget A:
Navigator.pushNamed(context, Routes.second); // Routes.second is the same as '/second'
// inside widget B:
Navigator.pushNamed(context, Routes.second);
// inside widget C:
Navigator.pushNamed(context, Routes.second);
Now if you need to pass a parameter to SecondRoute upon creation you can do it in a centralized location using the MaterialApp onGenerateRoute, as this tutorial explains in more detail. Your code will be changed to:
// inside widget A:
Navigator.pushNamed(context, Routes.second, arguments: 'Title A');
// inside widget B:
Navigator.pushNamed(context, Routes.second, arguments: 'Title B');
// inside widget C:
// You can still make mistakes here, but the chances are smaller.
Navigator.pushNamed(context, Routes.second, arguments: 'Title C');
MaterialApp(
onGenerateRoute: (settings) {
if (settings.name == Routes.second) {
final String title = settings.arguments;
return MaterialPageRoute(
builder: (context) => SecondRoute(title: title),
);
}
},
);
The amount of duplicated code is decreased, but on the other hand the onGenerateRoute code gets more complex as you make more routes, as all of their creation will be centralized there, so IMHO it's more about a personal preference then a general guideline.
Push and PushNamed have the similar effect, Push will switch to the route you specified while PushNamed will switch to the route with the route name specified.
What the Tutorial page means for duplication is duplication of code not duplication of routes.
For instance, you have a route where you would want to check whether the user is signed in and show the corresponding page
Using Push only:
Page1:
//This is page 1....
RaisedButton(
child: Text('Go to second'),
onPressed: () {
if (user.state = "login") {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SecondPage(),
),
)
}else{
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SecondPageAnonymous(),
),
)
}
}
)
....
In another page, Page2, you will need to repeat the same code:
//This is page 2....
RaisedButton(
child: Text('Go to second'),
onPressed: () {
if (user.state = "login") {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SecondPage(),
),
)
}else{
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SecondPageAnonymous(),
),
)
}
}
)
....
With PushNamed, you just have to declare it once and you can basically reuse it over and over again.
In your onGenerateRoute:
onGenerateRoute: (settings) {
switch (settings.name) {
case '/':
return MaterialPageRoute(builder: (_) => FirstPage());
case '/second':
if (user.state = "login") {
return MaterialPageRoute(
builder: (_) => SecondPage()
);
}else{
return MaterialPageRoute(
builder: (_) => SecondPageAnonymous()
);
}
default:
return _errorRoute();
}
},
Now in ANY pages in your project, you could do this:
Navigator.of(context).pushNamed('/second')
Without needing to repeat the checking of sign in or even the error handling every time you used it. The obvious benefit is that you can stay consistent throughout the app by preventing duplicate code piece, instead of repeating it again and again.
Now, this however DOES NOT prevent duplicates of routes! There is no different between push and pushNamed in this context!
But since your routes are now named, you can do popUntil('/') easily to go back to the first instance of the route, instead of creating it again or PushReplacementNamed.
The only advantage I can see using Navigate with named routes is to have routes declared inside your MaterialApp, so that developer can only be used assigned routes i.e widgets, pages,
If anyone uses other than that, It will give an error 'onUnknownRoute is called.'
Here is my beginner flutter thoughts:
It makes the code cleaner: Without declaring the routes at the level higher widgets, new screens will appear out of nowhere, in response to anything that happens in the app. It is much easier to understand the navigation skeleton/ structure when you declare the routes together, and even more so at a higher widget, especially for other developers. Of course, this doesn't help with understanding exactly when those routes are actually navigated to, but its a small improvement, and brings us back into the declarative paradigm. The hint provided by the declared routes will help a newer developer understand your navigation flow.
For folks visiting this question in 2022. Flutter actually now recommends not using named routes
Note: Named routes are no longer recommended for most applications. For more information, see Limitations in the navigation overview page.
https://docs.flutter.dev/cookbook/navigation/named-routes
If you use push(), you have to import the file in which SecondRoute is located every time you need to navigate to that screen. This is excessive code duplication for big projects that you need to navigate around the different screens.
If you use pushNamed(), you need to define the routes only once in your MaterialApp. Then, you can navigate to any screen from anywhere without repeating the same thing like you have to with push().
Another big reason to choose PushNamed() over the other one is to be able to build your own navigation system with it. You can decide whether or not routes are available for certain users even before they navigate to the screen.
for understanding why we should use Navigator.pushNamed instead Navigator.push first let's be familiar with Navigator methods. did you ever heart about Navigator.popUntil or Navigator.pushAndRemoveUntil?
we use Navigator.popUntil when we want to pop in the stack to a specific route. if you check the documentation you can find that it's very easy to use these methods with the pushNamed method. also, check all methods in the documentation. when I try to understand routing in flutter this article was very useful for me.
and as a disadvantage, it's very hard to handle parameters in this approach. you should create onGenerateRoute and handle parameters for each route.
I'm implementing an authentication flow in my Flutter app.
After a sign in attempt, the CheckAuth (which checks whether a user is signed in or not and then opens home screen or sign up screen accordingly) is opened with this code:
void _signIn() async {
await _auth
.signInWithEmailAndPassword(
email: _userEmail.trim(), password: _userPassword.trim())
.then((task) {
// go to home screen
if (task.getIdToken() != null) {
setState(() {
Navigator.pushReplacement(
context,
new MaterialPageRoute(
builder: (BuildContext context) => new CheckAuth()));
});
} else {
print("Authentication failed");
}
});
}
Problem: I can successfully sign in to the app, but if I tap back button after I sign in, it goes back to the sign in screen (while I expect it to exit from the app).
Question: How to move from one screen to another in Flutter without the way back?
Do I need to somehow delete the navigator history? Or don't use navigator at all? I tried Navigator.replace method, but it didn't seem to work.
You need to use Navigator.pushReplacement when leaving the auth screen too. Not just when redirecting to login page.
You can use the pushAndRemoveUntil method:
Push the given route onto the navigator that most tightly encloses the given context, and then remove all the previous routes until the predicate returns true. To remove all the routes below the pushed route, use a [RoutePredicate] that always returns false (e.g. (Route<dynamic> route) => false).
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => MainPage()),
(Route<dynamic> route) => false,
);
You need to use
Navigator
.of(_context)
.pushReplacement(new MaterialPageRoute(builder: (BuildContext context) => page));
Where _context is object of BuildContext
And page is which page you directed to.
I think you probably have already solved this. But you can set "automaticallyLeadingImplied: false" in the AppBar of the Scaffold you are navigating to.
Just simply add the below code:
Navigator.of(context).pushNamedAndRemoveUntil('/routeName', (route) => false);
We can use routes for the same
Like:
routes: {
LoginScreen.route_name: (_) => LoginScreen(),
.....
},
And use below code whenever you want to push and remove the backstack
Navigator.of(context).pushReplacementNamed(LoginScreen.route_name);
Note: Define static String inside widget LoginScreen
I have resolved this by popping from current page and showing new page:
Navigator.pop(context);
Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (context) => newPage));
Here is the solution -
Use pushAndRemoveUntil instead of pushReplacement
Also, can be use maintainState: true
For set root page
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (context) => pageName, maintainState: true),
(Route<dynamic> route) => false);
For Push Page one page to another
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => pageName,
maintainState: false),)
**If you want to refresh always while appearing page then use: **
maintainState: false
For anyone wondering there is a new argument that needs to be returned false Navigator.of(context).pushNamedAndRemoveUntil('/', (route) => false);
Navigator.of(context, rootNavigator: true).pop();
Navigator.pushNamed(context, '/');
It was not working for me because I was using the home proptery rather than initialRoute on the MaterialApp.
This is a link where there's the folloing warning which helped me to spot the error: https://docs.flutter.dev/cookbook/navigation/named-routes#2-define-the-routes
Warning: When using initialRoute, don’t define a home property.
if you are working with Getx state managemaent then you can try this
Get.of(()=> NewPage());