Pop a screen in between in Flutter - 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");

Related

Return data when a screen is closed

I have several sub-screens which give the user the option to save some data. When that screen is closed, I want the parent screen, which pushed the sub-screen, to know whether data was saved or not. The sub-screens maintain a didSave flag and are set to true when data is saved.
There are several ways for the sub-screens to be closed:
hardware/software back button.
The close button on the AppBar.
A button on the screen itself.
I can handle the 3rd case using Navigator.pop(context, didSave) and in the parent that didSave flag is captured using final didSave = await Navigator.push<bool>(context, myRoute).
However, for the first 2 cases, the result will obviously be null.
I have looked into WillPopScope but that only is used to determine whether the screen should be closed or not. It doesn't seem that I can set any data to be returned for the push call.
I have also looked into wrapping the parent screen in a Provider where it can listen to didSave states but that will trigger immediately when emitted which is not desirable for my use case. I only want to act when the sub-screen is closed not when data is saved.
I can potentially use WillPopScope and emit an event there if a save operation has occurred but I would like a simpler solution if available.
Is there a way for this that I am missing?
Thanks!
as you said the Provider will listen to didSave and this doesn't fit in your case you can use just a simple inheritedWidget:
wrapping the parent like this:
InheritedExampleWidget(
didSave: false,
child: Parent(),
),
you need to set a setter to didSave
then on the ascendant widgets on the widget tree, you can:
InheritedExampleWidget.of(context).didSave = true;
this will not trigger it immediately, which the Provider package solves.
then you can manage the state however you want
Did you try to create the variable with state management? And there is method with the push that do task after user come from the child screen. So, when they come you can checkout the use have saved data or not.
For EG:
saved = false; //State management variable
//We are pushing on another screen.
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) =>
new ScreenName(),
),
).then((val) {
//This part with work when user popped the screen on which we have pushed
if (!saved) {//Do Something when not saved}
});
Try above code and let me know if you get any error or you're facing any issue.
when you push a new route, using StatefulWidget, it will have a lifecycle starting from an createState, when the widget isn't there on the widget tree ( when we pop ), the dispose method will be called.
those cases of popping the route:
hardware/software back button.
The close button on the AppBar.
A button on the screen itself.
will trigger the dispose method to execute.
so you can put inside it your logic that you want.
exmaple :
class classTest {
bool didSave = false;
}
then when on the property where you want to push the screen set it to that classTest's didSave, as an example:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const YourRouteWidget(didSave: classTest.didSave,
),
),
);
on that point it's false, then when the user will complete using the screen, going back with any king of popping the route (with Navigator.pop(context), or with back button...), the dispose method will be called, so you can :
void dispose() {
if(/* your conditions*/ ) {
classTest.didSave = true;
}
super.dispose();
}
it will be back with a true value to the parent page.

How to pop the navigator two pages back?

I need to leave the current screen and navigate back to the second to last screen.
void leaveStatefulWidget3(){
Navigator.pop(context);
}
// StatefulWidget 2...
// StatefulWidget 3 {....}
// the current one (where the navigator is now)
My app has navigated from the first screen to 2, and then to 3.
I need to leave or Navigator.pop(context) the StatefulWidget 3 to stateFull 1.
NOTE : I have complete access to my leaveStatefulWidget3() method from StatefulWidget 3, but don't know how to specify the route to use Navigator.pop(context) in leaveStatefulWidget3().
When I use Navigator.pop(context) directly from the first widget it leaves stateFull 1.
just use Navigator.pop(context) twice
You could easily use 'popUntil' with 'pushNamed'
Add the route settings with route names when pushing,
like this.
Navigator.pushNamed(context, < StatefulWidget name >);
Then, at the recent widget ( in your case, its stateful_widget 3 )
Navigator.popUntil(context, ModalRoute.withName("/"));
*Here you can use widget name or '/'
*'/' will bring you back to the startup widget
Or , if this is always from certain widget to very first one
You could directly use
Navigator.of(context).popUntil((route) => route.isFirst);

Flutter, how to update reactive list declared in first screen controller from the second screen?

I am using GetX in my project. Lets suppose I have two screens. Each screen have a button.
The controller of first screen have observable list of objects. When a button in first screen is pressed, one of the object list is sent to second screen.
In second screen user changes the value of the data received and when save button is pressed, I want to update the observable list in first screen as well.
I know I can use Get.back() to send updated data back to first screen. I could do following in second screen
Get.back(result: [
{"updatedObject": listDetails}
]);
And in first screen I could do
Get.to(() => SecondScreen(), arguments: [
{"list": listDetails[index]}
]).then((result) {
// result[0]["updatedObject"] // use this to update list observable
});
My Question.
Can I update list in first screen without navigating back to first screen when Save button is pressed.
I am pretty new with GetX and i am trying to learn it. Thanks
As long that when you navigate to the second screen using Get.to() then you can always access the data in the controller of the first screen using Get.find().
Here's a sample I tweaked it a little bit:
Get.to(() => SecondScreen(), arguments:
{"index": index},
)
On the controller on the second screen you can access(view/update) the data using the index.
#override
void onInit() {
index = Get.arguments['index'];
// View
name = Get.find<FirstScreenController>().listDetails[index].name;
print(name);
// Update
Get.find<FirstScreenController>().listDetails[index].name = "John";
}

In Flutter Which method gets called when pop back to Screen A from Screen B

I have updated my Sqlite Table in Screen B and want to reflect that change in Screen A Also.
So when Pop back from Screen B> Screen A nothing updated.
In Android, we have onResume() to make changes reflect in Activity A.
I have used Widgets Binding Observer but it works if app background, foreground.
Navigator.of(context).pop();
You can await Navigator.push, when pop is called anything below Navigator.push will be called. Ex:
await Navigator.push(SomePage());
/// This will be called after SomePage cals Navigator.pop();
doSomething();
You can get back from the second page with data like this:
// Second Page
Navigator.pop(context, 'result');
Then by the below code on the first page, you are able to lunch the second page and wait until return from it and get data.
// First Page
var result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondPage()),
);

How to finish current view / activity in flutter?

Is there any method similar to this.finish() in android to finish current flutter activity.
Do you mean close current screen and come back to previous screen?
If that, Navigator.pop(context) should do the work. However, if you are at the entry screen (the first screen of the app and this screen has no parent screen/previous screen), Navigator.pop(context) will return you to a black screen. In this case, we have to use SystemNavigator.pop().
But don't use SystemNavigator.pop() for iOS, Apple says that the application should not exit itself :)
Below code will work on Android for both cases
if (Navigator.canPop(context)) {
Navigator.pop(context);
} else {
SystemNavigator.pop();
}
use Navigator.pushReplacement(BuildContext context, Route<T> newRoute) to open a new route which replace the current route of the navigator
use thie code
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) {
return AddDemoUnitActivity();
}));