Is there any official way to cancel a route navigation from the onGenerateRoute method?
Right now I've "solved" it by just returning null, which kind of works but generates error messages in the log.
Route _onGenerateRoute(RouteSettings settings) {
Widget page =
settings.name == 'page1' ? Page1() :
settings.name == 'page2' ? Page2() :
null;
if( page == null ) return null;
return MaterialPageRoute(
builder: (ctx) => page,
settings: settings
);
}
So I'm wondering if there is a proper way to cancel a navigation - and just stay on the current route - from onGenerateRoute?
Related
This structure of my app navigation:
CupertinoTabScaffold ->tabBuilder:TabPage-> tabBar: CupertinoTabBar
My TabPage:
return MaterialApp(
navigatorKey: navKey,
home: child,
);
I have separate navigation in each tab. by clicking on the bottom tab, I go back to the beginning. this is my code and this is working now:
key.currentState!.pushNamedAndRemoveUntil('/', ModalRoute.withName('/'));
for each tab I have my own navigation key. I pass it to each TabPage.
but if the page is at the root I need to avoid pushing same route twice. I tried this code but it doesn't work:
key.currentState!.pushNamedAndRemoveUntil("/",
(route) => route.isFirst && route.settings.name == "/" ? false : true);
how avoid pushing same route twice?
Navigator.of(context).pushNamedAndRemoveUntil(
"newRouteName",
(route) => route.isCurrent && route.settings.name == "newRouteName"
? false
: true);
t will pop the current route if name of the route is "newRouteName" and then push new one, else will not pop anything.
Edit: Latest flutter provides ModalRoute.of(context).settings.name to get the name of current route.
If you're using a BottomNavigationBar, you can simply add a checker on onTap() to see if you're going to navigate on the same page. To track the current page, you can either use enum or a simple variables like int. CupertinoTabBar also have an onTap property available similar to BottomNavigationBar.
BottomNavigationBar(
items: <BottomNavigationBarItem>[...],
currentIndex: _currentPage,
onTap: (value){
// Only navigate if a different page was clicked
if(value != _currentPage){
_currentPage = value;
setState(() {
switch(value) {
// Page 0
case 0:
key.currentState!.pushReplacementNamed(...);
break;
// Page 1
case 1:
key.currentState!.pushReplacementNamed(...);
break;
...
}
});
}
}
)
Or if you're using a TabBar with TabBarView, the page displayed can be managed by the controller as demonstrated in this guide.
This will pop the current route if name of the route is the new Route Name and then push new one, else will not pop any route.
Navigator.of(context).pushNamedAndRemoveUntil(
"new",
(route) => route.isCurrent && ModalRoute.of(context).settings.name == "new"
? false
: true);
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 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;
);
I have a flow to open the apps using navigation like this:
Profile Page(1st) -> List Attachments(2nd) -> List Resumes(3rd) -> Edit Resume(4th)
Then, from "Edit Resume(4th)" I want to go to "List Attachments(2nd)" with "remove the navigation state from 2nd 3rd 4th". So when I success navigate and open the "List Attachments(2nd)" screen, and when I click the back button from AppBar (in the "List Attachments(2nd)" screen) or from the device (in the "List Attachments(2nd)" screen), it will not back to "Edit Resume(4th)", but will back to "Profile Page(1st)". How to do that?
I already try with this:
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => AttachmentsListPage(widget.userEntity)),
(Route<dynamic> route) => false,
);
It removes my back button on the AppBar in the "List Attachments(2nd)".
I also try with this:
Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (BuildContext context) => AttachmentsListPage(widget.userEntity)));
But still not working.
Fyi, I'm not using route.
If you want to use this then you will have to return true where you want to stop rather than return always false.
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => AttachmentsListPage(widget.userEntity)),
(Route<dynamic> route) => route is ProfilePage,
);
Another thing you can do is, call
Navigator.pop(context); //will return to the previous screen
Navigator.pop(context); //will return to two screens before
And you can use this as many times you need to go back to.
try using this to move to the second page (set the route name).
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => SecondPage(),
settings: RouteSettings(name: '/second'),
),
);
then on the fourth page use this to try to return to the second page without losing the second page state if not found then return to the main page (initial route).
Navigator.popUntil(
context,
(Route route) {
String name = route.settings.name;
bool secondPage = name == '/second';
bool initPage = name == '/';
return !initPage ? secondPage : initPage;
},
);
and this is the result.
I fix this using popUntil.
Navigator.popUntil(context, ModalRoute.withName(routeName))
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());