Flutter - how avoid pushing same route twice? - flutter

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);

Related

Cancel route navigation in onGenerateRoute

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?

Flutter - navigate back to specific tab on a page with Navigator.pop(context)

In my app, I have a homepage that has 5 tabs on the bottom. On each tabbed page, there is an app bar that has a '+' symbol as an action, which navigates you to a different page. The navigation with that '+' button to the new page is done with the following code, alongside the Flutter Platform Widgets package:
Navigator.of(context, rootNavigator: true)
.push(
platformPageRoute(
context: context,
builder: (context) => Page1(),
),
);
I use the platformPageRoute feature as an easy way to navigate with a native feel. Now, that works fine to navigate to a new page, but the issue comes when I use
Navigator.pop(context);
to navigate back to the original page. When I use that to navigate back to that original page, it pays no attention to the tab that was selected originally. For example, if I were originally on the second tab on the homepage and then use the '+' button on that tab and then finally use
Navigator.pop(context);
on that new page, it returns the first tab of the homepage. Is there any way of ensuring when I use the above command, it goes to the right tab? I have tried something along the lines of:
Navigator.popUntil(context, '/homepageTab2');
alongside a named route, to return to the correct tab on the homepage, although that returns a black screen. Why might that be? I have also tried using:
Navigator.pushAndRemoveUntil(
context,
platformPageRoute(
context: context,
builder: (context) =>
HomePage(selectedPage: 1),
),
(route) => false,
);
This does not work either, since it returns the selected/correct page tab content, but with the first tab selected. In addition, the other
'problem' for me is that the animation is a 'push' one and that doesn't 'match' with the animation when I have more often used
Navigator.pop(context);
to navigate back to a screen. Is there a way to maybe use pushAndRemoveUntil but then change the animation to match a pop animation?
Thanks!
EDIT:
I have just noticed that with the situation I have described above, it is actually returning the correct screen content when I use Navigator.pop(context); but the tab in the tab bar at the bottom is showing as the first tab, in the second tab's position, essentially duplicating the first tab, until I navigate to a new tab and back, at which time it shows the correct tab in the correct position. I hope that makes sense!
As it turns out, the issue wasn't related to Navigator.pop(context); being used. It was the way I was controlling the selected tab. I'm posting this as an answer incase it helps someone else.
Initially, I created late values for a tab controller and the current selected page, like so:
late TabController _tabController;
late ScrollController _scrollController;
late int _selectedPage;
Then, I created a list of widgets that represented the actual page to display for each selected tab:
List<Widget> _pageWidgets = <Widget>[
Page1();
Page2();
Page3();
Page4();
Page5();
];
Then (and I think this was the bit that wasn't working) I used initState() as follows:
void initState() {
super.initState();
// Initialising a value that allows the 'final' page selector to be changed
_selectedPage = widget.selectedPage;
// Initialising the tab controller
_tabController = TabController(
length: 5,
vsync: this,
initialIndex: _selectedPage,
);
// updating the tab index when a new item is selected
_tabController.addListener(() {
setState(() {
_selectedPage = _tabController.index;
//_tabIndex = _tabController.index;
});
});
// Creating the scroll controller
_scrollViewController = ScrollController();
// Scrolling view to top when a new tab is selected
_tabController.addListener(() {
setState(() {
_scrollViewController
.jumpTo(_scrollViewController.position.minScrollExtent);
});
});
}
I then controlled the page content like this:
body: _pageWidgets.elementAt(_selectedPage),
I'm not 100% sure why this wasn't working, although I believe it would have something to do with the fact that initState() would only be called during the build and therefore placing the functionality inside there would mean changes wouldn't be detected. Either way, my new method, which works perfectly, is:
/// Controls the screen to display first
int _index = 0;
/// Creating a navigation key to control tab bar navigation
final _navigationKey = GlobalKey<CurvedNavigationBarState>();
Then, within the Scaffold() I show the page content like this:
body: _pageWidgets.elementAt(_index),
And finally, within the navigation bar (which is the CurvedNavigationBar() package from pub.dev) I give it a key and the index:
key: _navigationKey,
index: _index,
And this controls it perfectly, showing the correct tab.
Sub-pages of a TabBarView cannot be navigated using Navigator.
You can use TabController to go to your desired tab page after awaiting Navigator.push():
await Navigator.of(context, rootNavigator: true)
.push(
platformPageRoute(
context: context,
builder: (context) => Page1(),
),
);
tabController.animateTo(<index of tab>);

How to pop 2 screen at once in flutter

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();

Flutter GetX routing history

I it possible to see the navigator stack with GetX? I looked in the documentation but I could not find anything on this subject. I usually close for example dialogs like this
Get.until((route) => !Get.isDialogOpen);
But I was wondering if I could close routes if an instance of a specific page is in the routing history which would be something like this
Get.until((route) => !Get.routingHistory.contains('/someRoute'));
Note this isn't valid syntax.
You need to use:
Get.offUntil(page, (route) => false)
page means the new page to navigate.
(route) => false
Is the condition.
Get.until
Remove screens until satisfying the condition.
It’s the same with Navigation.popUntil().
You can use it like Get.until((route) => Get.currentRoute == '/home').
Get.offNamed
By the Named route, remove the current screen and add a new screen.
It’s the same with Navigation.pushReplacementNamed().
You can use it like Get.offNamed('/second').
Get.offAndToNamed
By the Named route, add a new screen and then, remove the previous screen.
It’s the same with Navigation.popAndPushNamed().
You can use it like Get.offAndToNamed('/second').
Get.offUntil
Remove screens until satisfying the condition, and then, add a new screen.
It’s the same with Navigation.pushAndRemoveUntil().
You can use it like Get.offUntil(page, (route) => (route as GetPageRoute).routeName == '/home').
Get.offNamedUntil
By the Named route, remove screens until satisfying the condition, and then, add a new screen.
It’s the same with Navigation.pushNamedAndRemoveUntil().
You can use it like Get.offNamedUntil(page, ModalRoute.withName('/home')).
Please use according to your usecase
GetX have another useful function:
int times = 2;
Get.close(times);
Close as many routes as defined by [times]
If you want to keep closing routes until you reach a page route....
Navigator.of(context).popUntil(ModalRoute.withName('/route-name'));
It is possible. Navigator.popUntil pops pages until a passed predicate returns true. We can query the following route in the navigator stack and decide what decision to make.
The GetX method for doing the same is
`
Get.offUntil( MaterialPageRoute(builder: (context) => const NewPage()), (route) {
var currentRoute = route.settings.name;
debugPrint("Get.currentRoute --- $currentRoute");
if(currentRoute == "/Home") {
return true;
} else {
return false;
}
}
`
The code above pops until home. Also, we can add custom logic in the if-else block above.
Get.until((route) {
if (route.settings.name == Routes.TEST1) {
//Return to the specified page
return true;
} else {
return false;
}
});
Get.offAll(Home()); // remove all previous routes and redirect to home
of with namedRoutes:
Get.offAllNamed('/home');

Flutter: How to avoid pushing the same route on existing route

I have view with bottom navigation bar, and when you push a navbar item, a new route is pushed into view.
final navigatorKey = GlobalKey<NavigatorState>();
#override
Widget build(BuildContext context) => MaterialApp(
home: Scaffold(
key: _scaffoldKey,
body: _buildBody(context),
bottomNavigationBar: _buildBottomNavigationBar(context),
),
);
void routeToView(routeIndex) {
navigatorKey.currentState.pushNamed(pagesRouteFactories.keys.toList()[routeIndex]);
}
I would like to prevent same route being pushed on the current view. I want to compare the current route with new route we are trying to push, and ignore pushing the route if it is same.
I want to scroll the view to the top if it is same route we are trying to push
Any ideas.
NavigatorState doesn't expose an API for getting the path of the
current route, and Route doesn't expose an API for determining a
route's path either. Routes can be (and often are) anonymous. You can
find out if a given Route is on the top of the navigator stack right
now using the isCurrent method, but this isn't very convenient for
your use case.
https://stackoverflow.com/a/46498543/2554745
This is the closest solution I could think of:
Navigator.of(context).pushNamedAndRemoveUntil(
"newRouteName",
(route) => route.isCurrent && route.settings.name == "newRouteName"
? false
: true);
It 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.
This works for me:
Route route = ModalRoute.of(context);
final routeName = route?.settings?.name;
if (routeName != null && routeName != nav) {
Navigator.of(context).pushNamed(nav);
print(route.settings.name);
}