Wait for Navigator.pop ignoring Navigator.pushReplacement - flutter

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.

Related

How to call a function when returning to view/controller with `Get.back()`?

I am switching from the home view to another using Get.toNamed(Routes.DETAIL). When I want to return from the details view to the home view, I am calling Get.back() (or the user is using the back button of the devices).
Back on the home view, I would like to fetch all data from my database again.
Is there any function that is triggered when I am leaving a few and returning to it, so I can put my logic there?
Thank you
I would suggest you to use Get.offNamed() instead of Get.toNamed() as the offNamed() function will clear the data stored in catch and thus will again call the API declared in onInit() or onReady() lifecycle when returning back to that screen.
In Getx they have a funtion like
Get.back(result:"result");
so in order to trigger some funtion when going back to any page route
try doing this as the document written
final gotoHome = await Get.toNamed(Route.name); // or use the simple one Get.to(()=> Home());
then if you trigger to go back in page you should indicate some result e.g.
from back button in phone using willpopscope or a back button in UI.
Get.back(result:"triggerIt"); // this result will pass to the home.
so in will use
// It depend on you on where you gonna put this
// onInit or onReady or anything that would trigger
someTrigger() async{
final gotoHome = await Get.toNamed(Route.name);
if(gotoHome == "triggerIt"){
anyFuntionYouwantoTrigger();
}
}
for more info about it try to read the documentation.
https://github.com/jonataslaw/getx/blob/master/documentation/en_US/route_management.md
Edited: // Maybe some answer will pop up for better
I do have one but it's not that quite a real practice just a sample
e.g // sample you are now in the current page and this page is also connected to homecontroller or using Get.find() it need to bind the controller to the page;
class BindingHome with Bindings{
#override
void dependencies() {
Get.lazyPut(() => HomeController(), fenix: true);
}
}
then from GetPage add Binding
GetPage(
name: "/currentpage",
binding: BindingHome(),
page:() => HomeView(),
),
so while homecontroller is bind to the current page you are now so
// lets assume this one is put to the CurrentController
final homeController = Get.find<HomeController>();
so while calling back button on ui or willpopscope
when back try to trigger the function from home
gotBackfunction(){
Get.back();
homeController.anyFuntionYouwantoTrigger();
}
No you can't really call a function when doing so.
You should be using callback function just before popping the view.
onBackClick() async {
Get.lazyPut<MainController>(
() => MainController(),
);
controller.allItems.refresh();
Get.back();
}
Here is an example how you can do it without any complications:
WillPopScope(
onWillPop: () async {
await onBackClick();
return Future(() => false);
},
child: Scaffold(
appBar: CustomAppBarWithBack(
title: "Appbar",
OnClickBack: onBackClick,
),
body: Widget(),
),
);

Flutter - re-run previous page code after execution returns to it

I'm trying to figure out the best way to implement the proper navigation flow of a flutter app I'm building that involves a 3rd party authentication page (Azure AD B2C). Currently I have a page that serves simply as a "navigate to 3rd party auth login" page which is set as the initialRoute for my app. The first time through, it runs exactly the way I want it to, but I'm not able to figure out how to get that 'navigate to auth' page to re-run when navigated back to (after logout) so that the user ends up back at the 3rd party auth login page.
Basically what I'd like to do is, on logout - have the app navigate back to that page specified as the initialRoute page, and upon that page being navigated back to, have it re-launch the 3rd party auth login page just like it did the first time it executed.
I tried just awaiting the call to Navigator.push() and then calling setState((){}) afterwards, and that does re-display the page, but it just leaves that initial page sitting there, and doesn't end up triggering the execution the way it did the first time. initState() does not fire again, so neither does any of my code that's in there.
I've tried various methods off the Navigator object trying to reload the page or navigate to itself again, or just calling goToLogin() again after the await Navigator.push() call, nothing works.
Here's what I'm currently doing :
User launches the app, the initialRoute is LoginRedirect
class LoginRedirect extends StatefulWidget {
#override
_LoginRedirectState createState() => _LoginRedirectState();
}
class _LoginRedirectState extends State<LoginRedirect> {
#override
void initState() {
Utility.getConfig().then((value) {
config = value;
oauth = AadOAuth(config);
goToLogin(context);
});
super.initState();
}
void goToLogin(BuildContext context) async {
setState(() {
loading = true;
});
try {
await oauth.login(); // this launches the 3rd party auth screen which returns here after user signs in
String accessToken = await oauth.getAccessToken();
navigateToDashboard();
setState(() {
loading = false;
});
} on Exception catch (error) {
setState(() {
loading = false;
});
}
}
void navigateToDashboard() {
await navigator.push(context, MaterialPageRoute(builder: (BuildContext context) => Dashboard()));
// right here is where I'd like to call goToLogin() again after I Navigator.popUntil() back to this
// page, but if I try that I get an error page about how 'The specified child already
// has a parent. You must call removeView() on the child's parent first., java.lang
// .IllegalStateException and something about the bottom overflowed by 1063 pixels
}
}
After getting some config values and calling oauth.login() then I call a navigateToDashboard() method that pushes the Dashboard page on to the navigation stack.
Elsewhere in the code I have a logout button that ends up calling this code:
oauth.logout();
Navigator.popUntil(context, ModalRoute.withName('/LoginRedirect'));
which returns execution to where I called await Navigator.push() previously. But I can't figure out what I need to do there to have that LoginRedirect page execute again. I can't call goToLogin() again or it errors/crashes. I can't call initState() again, calling setState() doesn't do anything. I'm kinda stumped here, I thought this would be easy.
When logging out try: Navigator.pushReplacementNamed(context, "/LoginRedirect"); instead of Navigator.popUntil(context, ModalRoute.withName('/LoginRedirect'));

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.

pop and push the same route back with different params in Futter (GET X)

I have 2 screens,
Screen one contains a list view with onPressed action on every item
screen two contains the detail of the pressed item as well as a drawer with the same list view as screen one.
What I want to do here is when the user goes to the detail screen and click on an item from the drawer the detail screen should pop and push back with new params.
Code so far,
Route
GetPage(
name: '/market-detail',
page: () => MarketDetail(),
binding: MarketDetailBinding(),
),
Binding
class MarketDetailBinding extends Bindings {
#override
void dependencies() {
Get.lazyPut(() => MarketDetailController());
}
}
Click action in screen one
onTap: () {
Get.toNamed('market-detail',
arguments: {'market': market});
},
Detail Screen Class
class MarketDetail extends GetView<MarketDetailController> {
final Market market = Get.arguments['market'];
}
Click action in detail screen sidebar
onTap: () {
Get.back();
Get.back();
Get.toNamed('market-detail',
arguments: {'market': market});
},
First Get.back() is to close the drawer, then remove the route and push the same route back again,
Expected behaviour,
MarketDetailController should be deleted from memory and placed again,
What actually happening
The controller only got delete and not getting back in memoery on drawer click action until I hot restart the app(By clicking save).
If anybody understands it, please help I am stuck here.
As I can see, you're trying to pop and push the same route with a different parameter in order to update a certain element on that route. Well, if that's the case then just let me show you a much better way.
In your MarketDetailController class you should add those:
class MarketDetailsController extends GetxController {
// A reactive variable that stores the
// instance of the market you're currently
// showing the details of.....
Rx<Market> currentMarket;
// this method will be called once a new instance
// of this controller gets created
// we will use it to initialize the controller
// with the required values
#override
void onInit() {
// some code here....
// .......
// intializing the variable with the default value
currentMarket = Market().obs;
super.onInit();
}
void updateCurrentMarket(Market market) {
// some code here if you need....
// updating the reative variable value
// this will get detected then by the Obx widgets
// and they will rebuild whatever depends on this variable
currentMarket.value = market;
}
}
Now inside your page UI, you can wrap the widget that will display the market details with the Obx widget like this:
Obx(
() {
final Market currentMarket = controller.currentMarket.value;
// now you have the market details you need
// use it and return your widget
return MyAwesomeMarketDetailsWidget();
},
)
Now for your click action, it can just be like this:
onTap: () => controller.updateCurrentMarket(myNewMarketValue)
This should be it. Also, I advise you to change GetView to GetWidget and Get.lazyPut() to Get.put()

How to return data when popping multiple screens?

I know I can return data to the previous screen by using
Navigator.pop(context, 'Value');
But in my case I need to pop multiple screens by using
Navigator.popUntil(context, ModalRoute.withName('/login'));
I wonder in this case how do I pass the data back to the corresponding widget?
Thanks in advance.
you can send DATA in few ways
as a Parameter
using Shared_Preferences
using Static Variables
Only for Current Session
if you just need the DATA for Current Session you can go for Static Variables
step 1 : Create a Class and have Static Variable in it.
class Globaldata{
static String value;
}
step 2 : Initialise variable by
Globaldata.value="some_value";
step 3 : use of variable
String assigned_value = Globaldata.value;
The flutter API does not have that feature and from this https://github.com/flutter/flutter/issues/30112 discussion, that feature is not on the table yet. A walkaround was suggested though using the Page API.
However, in my opinion, it is cleaner to use the provider package https://pub.dev/packages/provider as part of your app state management to keep the data you want and make it available to any screen of interest. Follow these steps to achieve that.
Add the provider to your pubspec.yaml. Check the link above to see detailed instructions.
Create a notifier class that extends ChangeNotifier class as shown below. ChangeNotifier class is part of the flutter API.
class MyDataProvider extends ChangeNotifier {
//define your private data field(s). I'm using int here.
int _mydata;
//define a getter
int get myData => _myData;
// define a setter
set myData(newData){
_myData = newData;
notifyListeners();
}
}
Wrap your uppermost widget (or the parent of the screens where you want to pass the data) with the provider and instantiate it. I'm using main here.
void main(){
runApp(
ChangeNotifierProvider(create: (context) => MyDataProvider()),
child: MyApp(),
)
}
Assuming you have five screens: Screen1, Screen2, ..., Screen5 and you want to navigate to screen5, do some operations and return to screen1 with some data. On 1st screen, define a local variable for myData and create an instance of your myDataProvider. When a button is pressed to start the navigation, wrap up the push navigation in an asynchronous call.
//Screen1
int currentLocalData = 78;
MyDataProvider myProvider = Provider.of<MyDataProvider>(context);
onPressed: () async {
//Assign localData to myData in the provider
myProvider.myData = currentLocalData; //calls the setter define in the provider.
await Navigator.push(context, MaterialPageRoute(
builder: (context) => Screen5()
));
//Retrieve myData from the provider and assign it to currentLocalData.
//This executes after navigating back from Screen5
currentLocalData = myProvider.myData;
}
Let assume in Screen5 you retrieved the data and added 100 to it. Your aim is to return to Screen1 and make use of the new data, i.e 178. Here you will instantiate the provider again in Screen5 and assign the value 178 to myData.
//Screen5
MyDataProvider myProvider = Provider.of<MyDataProvider>(context);
myProvider.myData += 100;
//Use navigation.popUntil
Navigation.popUntil(context, ModalRoute.withName('/Screen1'));
Say you have Screen A,Screen B, Screen C. If you want to pop till Screen A and pass some data. Here is what you have to do.
1. Navigate from Screen A to Screen B
Navigator.pushNamed(context, '/screenb')
.then((value) {
//you will get return value here
});
2. To pop till Screen A.
//add thi code in Screen C
var nav = Navigator.of(context);
nav.pop('refresh');
nav.pop('refresh');