Problem with using `Navigator.pop` and setState in same method - flutter

I have stateful widget that is basically list of tasks. In that widget i also have a button that causes Dialog box to appear.
Dialog box is AlertDialog that consists of text field that has controller inside of it and Save button. Button calls state's method that does few things:
dialog box to pop so to get back to list of tasks
causes setState of stateful widget with value from controller
controller to clear
Method looks like this:
void saveNewTask() {
Navigator.of(context).pop();
setState(() {
toDoList.add([_controller.text, false]);
});
_controller.clear();
}
This method is passed all the way down to Save button, and Dialog box is of course seperate widget so Navigator gets the right context.
Considering the order in saveNewTask i would assume the order would go like this:
Dialog box gets poped off
setState gets called, stateful widget gets rebuild
controller is cleared.
but in slow motion i can see this operations don't happen in this order. Controller is cleared and widget is rebuilt before navigator pops off dialog box.
Before cliking the Save Button : -
After clicking the Save Button : -
(and after the second picture dialog box gets removed)
Digging through source code i found out that pop causes another setState of navigator's state that removes the Dialog box.
I thought that maybe popis asynchronous considering the behavior but Future is not returned so i can't use asynchronous function or thento enforce the order. setState is of course not asynchronous so i don't have a clue what's happening here??
Any clue is greatly appreciated.

The procedure you're following is not the actual way to do it.
Reasons:
AlertDialogue and the other dialogues you show are not a part of your main screen widget. It's a totally different widget. So you can't actually setState() of your widget from here.
Now you're trying to set the state of the dialogue widget which is already being removed when you say Navigator.pop();. Which is not possible. Cz that widget doesn't exist anymore.
In normal situations that controller would get destroyed too with the alert widget. But it's a good practice to manually destroy it.
So, to do it the right way these are the steps you should follow ->
First return the controller value to the main widget by passing it to the Navigator.pop(); function. In your case, just pass it for the save button value. Like this:
Navigator.pop(context, controller.text);
Now await on the showDialogue() method. And receive the value to the main widget. Something like this.
String? data = await showDialog(
context: context,
builder: (context) => AlertDialog());
Now in the main widget check if the dialogue returns a string or not. If it does, then set the state with the data you just received. And if it doesn't. In your case, when the cancel button is clicked. It will return null. Then don't do anything.
And finally, if you want to clear the controller for some reason. Then clear it before popping. And if you want to dispose it. Just dispose it inside the dispose method of your stateful widget.

Related

How to call a function from another page in flutter?

So I have a call a timer function on pressing the login button in the main file. The timer function is defined in the login verification file. So how can I call the timer function in main file so that when the login button is pressed and when it goes to the login verification the timer starts?
If you want to use some parent's function in child widget then it is easy - just pass the reference to the function you want through the constructor.
If you want to use some child's function in parent widget then the situation is a little bit different - you have to pass a GlobalKey to identify the state of child widget when creating an instance of this widget. You can refer to this https://stacksecrets.com/flutter/how-to-call-method-of-a-child-widget-from-parent-in-flutter.
If you want to use a function from a widget that is not either a child or parent widget, then you might think of using some state managment solution (such as Provider or GetX or Bloc) to provide it to all the widget that needs some information from it.

I want new (fresh) flutter page widget every time I open it

When I leave a page widget and come back to it later, it retains the previous info on the previous session. I would like to create a fresh page widget from scratch every time I opened it. How do I implement this?
I am guessing that your issue is the after going to new page using Navigator.push(), you get the previous state of the page after popping the new page.
In that case you can call a setState after popping the new page. Do the following :
Navigator.pop() on Page2 :
Navigator.pop(context,1); /// Passing an extra parameter (1) to Page1 after popping
Navigator.push() in Page1 :
Navigator.push(context,MaterialPageRoute(builder: (context) => Page2())).then((value){
if(value == 1)
setState(() {}); /// Here you can keep a conditional refresh like if you pass 1 in Navigator.pop() then only will it refresh the page
/// It's useful when you want to refresh the page only for specific conditions.
});
The .then() function after Navigator.push() will be called after Page2 is popped and as mentioned in that function, a setState() will be called refreshing the page.
Are you sure that when you are navigating through the pages you close them properly (end their life circle). If not try to navigate with Are you sure that when you are navigating through the pages you close them properly (end their life circle). If not try to navigate with pushReplacement instead of push
Putting the main data of the widget in the widget declaration works. I suppose the problem was the scope of the data used. If outside the widget declaration, the data would basically be global and would be the same every time the widget is navigated to.

How to keep count of how many times a page is loaded

I want to clear the form fields that remain filled if I click the back button to go back to that forms screen. Thus, basically i want to call a function that will ALWAYS be executed once a page is navigated to so that i can call the controller.clear() function. Thanku.
Can you call controller.clear() right before you navigate to a new screen from the forms screen. This way when they navigate back it will already be cleared.
if you want to call a function when a page is loaded for the first time. the initState of the state class is the place you wann do your calls. because the initState is the first method that gets called when a widget gets mounted on the screen, even before the first render.
#override
void initState(){
super.initState();
// call the functions you want to call
}

How to reload a page on Navigation.pop in Flutter?

I want to have page A reload, re-run the Http requests, once I pop back to it from another page that lets you edit the data page A displays. The other page is triggered by a long press in a ListView generated by a function within page A. IE:
Page A:
_PageAState
Reload function I need to call
Scaffold
List item
Text
ETC...
Expanded
ListView
Gesture Detector that navigates
ETC
How can I trigger a function of any sort or make the page reload when I pop back to it?
I've tried passing data from the pop and using an if statement but the nested structure messes it up.
of course, there's a way to do that!
As you realized, the first route needs to get notified when the pushed Route gets popped.
Thankfully, there's a tutorial in the Flutter documentation that handles returning data from Routes.
In your case, no data gets returned but you can still use the same principle to wait for the route to be popped: Simply await the Navigator.push call!
This will return once the other route gets popped.
await Navigator.push(
context,
MaterialPageRoute(builder: (context) => OtherPage()),
);
// TODO: Now, refetch the data.

How to make entire screen inactive (darken) under a popup widget in flutter (similar to showDialog)?

I have an "add" floating action button in Scaffold. When clicking the "add" floating action button, it will create two more floating action buttons above the original floating action button so that the user can choose which floating action button to click.
When the two more floating action buttons are active/popup, I want the entire screen to go blur/dark and inactive, similar to the effect to call showDialog(). So that only the three floating action buttons are active and all other screen parts are inactive and dark/blur.
And finally by clicking the inactive area, the two floating action buttons will be dismissed.
Thank you very much for your help.
I found two ways to achieve this.
With PopupRoute
I read flutter framework code and found out showDialog is actually using PopupRoute. Basically, this will create a new route and make the previous routepage inactive.
The simplest code is as follows:
class MyPopupRoute extends PopupRoute<void> {
#override
Color get barrierColor => Colors.black54;
#override
bool get barrierDismissible => true;
#override
String get barrierLabel => "Close";
#override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) => MyPopupPage();
#override
Duration get transitionDuration => const Duration(milliseconds: 300);
}
where MyPopupPage is the new popup widget.
If you want animations, you can override the method buildTransitions method. Simply check the API doc for more detail.
With Overlay
Thank #01leo very much for providing this way. The Flutter Challenge: Feature Discovery video has detail explanation on how to use it. Basically, just use Overlay.of(context).insert(overlayEntry); and the overlay is like a hidden stack on the top of the current page built-in.
At the time I am writing this answer, the code provided by the video author is not working in the latest fluter (dart 2). You have to wrap the overlay insert in a Timer such as Timer.run(() { Overlay.of(context).insert(calendarOverlay);}); to workaround the problem and make the code run.
By my findings, the reason why the author needs an async function call for overlay insert is because it couples the creation of overlay and the normal widget in a stateful widget. And when the state changes, the overlay insert will be called before building the child's widget and overlay needs to know the child's position but the child's widget is not created yet. If you don't want to async call overlay insert, you can simply decouple the creation of overlay and the corresponding normal widget. In such a way, you can simply call overlay insert normally. But with this sync way, you may not be able to find the normal widget position easily though.
(Btw, I use the word "normal widget" here, but it's not very correct. I haven't found a proper English word for it.)
I can't provide any code, but showDialog() and similar methods use the Overlay, integrated in Scaffold. It can be accessed by using Overlay.of(context). It essentially is a Stack around the whole Scaffold and you can insert items on top of everything.
For a more in-depth look into the Overlay and code examples I recommend watching this Flutter Challenge: Feature Discovery