Flutter GetX routing history - flutter

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

Related

Pop a screen in between in Flutter

I pushed three screens: ScreenOne > ScreenTwo(1) > ScreenTwo(2)
I'm at the second instance of ScreenTwo now, but I want to remove the first instance of ScreenTwo from the stack, so it should be ScreenOne > ScreenTwo(2).
When launching ScreenTwo(2) I know I shouldn't remove ScreenTwo(1) from the stack yet, so I can't just call Navigator.replace(). I really need to have ScreenOne > ScreenTwo(1) > ScreenTwo(2) for some time, and then remove the first instance of ScreenTwo(1).
How I can handle it? Navigator.pop() and similars only take into account the screen or screens on top of the stack.
If someone needs more context, this is for a phone app. Not an app for phones, but an app that mimics the behavior of a phone. So in reality, we have HomeScreen > CallScreen(Caller1) > CallScreen(Caller2). As the app can handle different calls at a time the first approach has been to map every call to a CallScreen and let every screen handle their own call events, so the first call can finish while the user is talking in the second one.
As discussed in this post:
How can I pop to specific screen in flutter
I quote:
If you didn't define any route in the MaterialApp then you need to define at the time of push.
Navigator.of(context).push(
MaterialPageRoute(builder: (_) {
return SecondPage();
},
settings: RouteSettings(name: 'SecondPage',),
));
You need to define the same route name
Navigator.of(context).popUntil((route){
return route.settings.name == 'SecondPage';
})
;
Or as an alternative if you did define the routes you can use that:
pushNamedAndRemoveUntil(
'/BottomNavigation', (Route<dynamic> route) => false);
In this case it is most suitable that you go from screen1 > Screen2 > replace previous with Screen3
Use this navigation from screen 2 to screen 3.
Navigator.pushReplacementNamed(context,"Third Screen");

Flutter go_router how to return result to previous page?

I'm trying to open a page and get returned result with go_router package.
In Navigation 1.0 I use this:
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SecondRoute()),
);
// handle result
But I can't seem to do it with go_router. Any solution or explaination?
You can do this with GoRouter.of(context).addListener.
First you push your new page and add a listener afterwards
GoRouter.of(context).push("/page/${page!.id}/edit");
GoRouter.of(context).addListener(watchRouteChange);
The listener function can look something like this
watchRouteChange() {
if (!GoRouter.of(context).location.contains("/edit")) { // Here you check for some changes in your route that indicate you are no longer on the page you have pushed before
// do something
GoRouter.of(context).removeListener(watchRouteChange); // remove listener
}
}
Presently there is no way to achieve this using go_router. You can use go_router_flow which is exactly like go_router with this pop with value feature.
final bool? result = await context.push<bool>('/page2');
WidgetsBinding.instance.addPostFrameCallback((_) {
if(result){
print('Page returned $result');
}
});
You can use the callback by putting the function in extra object when push new screen.
Example
Screen A -push-> Screen B ->pop with result -> Screen A (get results)
Define the function type to put
typedef AddNewEventResult = void Function(Result result);
Push A -> B
GoRoute(
path: kScreenB,
builder: (BuildContext context, GoRouterState state) => ScreenB(addNewEventResultstate.extra! as AddNewEventResult),
)
When screen B has done, just pop from B to A and attached the result (Result)
Navigator.of(context).pop();
widget.addNewEventResult(true, Result());
This flow is described in the docs here: https://gorouter.dev/user-input
Generally you have to update the data and return some value back as a route with params and the screen itself should manage updates / data manipulation.
I don't want to copy paste their code here, but the answer you are looking for is in the docs page above.
Updated with link from archive: https://web.archive.org/web/20220325235726/https://gorouter.dev/user-input
Thanks ahmetakil
When you are done with the next screen just use Navigator.pop(context,true); and true is something that you want to send to the previous screen. You can send anything I'm just using true for reference. This will allow your result variable to get data and perform anything.

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

Wait for Navigator.pop ignoring Navigator.pushReplacement

I have the following setup:
class FirstScreen {
// ...
Future<void> doSomething() async {
final bool isCool = await Navigator.of(context).pushNamed('/second-screen');
print(isCool ? 'Cool.' : 'Not cool.');
}
// ...
}
class SecondScreen {
// ...
Future<void> replace() async {
await Navigator.of(context).pushReplacementNamed('/third-screen');
}
// ...
}
class ThirdScreen {
// ...
Future<void> goBack() async {
await Navigator.of(context).pop(true);
}
// ...
}
However, this would crash, since the pushReplacement procs the await and my application won't wait until the pop is used.
How can I wait for pop 's value to be returned?
UPDATE:
The problem here is a little bit more complex than I told.
#Alok suggested to not pop the route but push it after the sequence, however, this is a very trivial version of my code.
I currently have a HomeScreen with a nested Navigator that pushes to a list of questions. Then, using Navigator.of(context, rootNavigator: true), I navigate to the examLoadingScreen, etc. (You can read about this in the comments)
If I push the HomeScreen when the exam is completed, I would lose all the navigation done in the mentioned nested Navigator.
I seriously need to pop in this scenario. I have multiple workarounds such as pop chaining but it doesn't seem very performant or convenient.
See, Zeswen, as far this documentation on pushReplacementNamed is concerned. It states that:
Replace the current route of the navigator that most tightly encloses the given context by pushing the route named routeName and then disposing the previous route once the new route has finished animating in.
Can you see that, it clearly mentions that it removes the previous route after you are done animating it.
Now, what are you trying to achieve is, or how Navigator.pop() value retrieval works, is it is mandatory to have that PrevoiusPage there when you move from one page to another
//What you're doing with pushReplacementNamed
1 -> SeconPage => ThidPage
2 -> SecondPage [Removed]
3 -> ThirdPage is trying to come to the previous page, that is SecondPage to return it's value, but SecondPage has been removed HENCE CRASHES!!
//What is needs to be done to use something like push() or pushNamed(), which used named route
1 -> SecondPage => ThirdPage
2 -> SecondPage is there in the stack
3 -> ThirdPage => SecondPage [Returns Value]
REMEMBER pop() always need the immediate precedence to accept it's value, not any page. So, if you remove the SecondPage, it will always crash.
Now, if you want to go to the page MainPage or in this case, FirstPage. Use pushAndRemoveUntil. It basically removes all the routes in the stack, and go to the page
SOLUTION: Pass the result score to the MainPage, via ResultPage. Make the MainPage accepts the Result Score too
class ThirdScreen(){
// ...
Future<void> goBack() async {
await Navigator.pushAndRemoveUntil(context,
MaterialPageRoute( builder: (context) => FirstPage(result: result),
(_) => false
);
}
}
And do your operation in your FirstPage accordingly, if you have result != 0 || result != null, and show it to the user. Let me know if that works out for you.
UPDATED ANSWER WITH A BEST POSSIBLE WORKAROUND
I have just added this answer, because, I feel like the above would be helpful in future as well.
Now, my idea is basic, and is workable according to the trivial information available for me.
THEORY: According to the theory, pop() value can be accessed by the predecessor only, immediate one.
SOLUTION
1. First Page -> Second Page
2. Second Page -> Third Page
3. Third Page -> Second Page with value
// Now following 3. step
1. Value check, if the value is true, pop immediately
2. Return the value to the first page
3. Print the value in the first page
Just follow your trivial data, and I hope you would understand the logic. After that implementation is just a cakewalk.
class FirstScreen {
Future<void> doSomething() async {
// We get the value from second page, which is technically passing
// the third page's value, and doesn't appear to us in UI
// So serving the purpose
final bool isCool = await Navigator.pushNamed(context, '/second-screen');
print(isCool ? 'Cool.' : 'Not cool.');
}
}
class SecondScreen {
Future<void> replace() async {
// No need of pushReplacementNamed, since we're are popping
// based upon our values, so it won't appear eventually
// and pass the value as well for the First Page
final bool value = await Navigator.pushNamed(context, '/third-screen');
// Now we check, whether what value we got from third page,
// If that is true, then immediately pop and return the value for first page
if(value == true){
Navigator.pop(context, value);
}
}
}
class ThirdScreen {
// async not required for performing pop()
// void is fine
void goBack() {
Navigator.pop(context, true);
}
}
Just check it. This logic will help you achieve the purpose, and it is safe and error free.

Flutter : How to refresh screen when user come back to it from any other screen

For ex.
I am on Screen-1 , push screen and goto Screen-2.
and then I replace Screen-2 with Screen-3
and now I pop that Screen-3 with pass some data
and come back to Screen-1
and I want refresh data in Screen-1
You can use something like this (but you must go from 3 to 2 and finally 1) to get some value then refresh it:
var result = await Navigator.of(context).push(
MaterialPageRoute(builder: (context) => Screen2()));
if (result != null) {
setState(() {
valueYouChangeIt = result;
});
}
And then send this value back from Screen2 with:
_sendDataBack(BuildContext context) {
Navigator.pop(context, valueToSendBack);
}
Or if you want to refresh everything you can write _initState function where you initialize your data and then call _initState on result.
Said that, all this can be super tedious process, so if it's a small app with 2 to 5 screens it can be ok, but with more screens, like someone else commented, I'd prefer to use Provider.