How to go back to the previous screen and reload all function of the first screen? - flutter

I have a problem. How to go back to the previous screen and reload all function of the first screen?
On page 1, I have several functions to load all data from json and calculations.
And when user goes to page 2 and does an insert to the json and when the suer goes back to page 1, reload all the functions on page 1.
I have function like below:
Future<void> CountBalanced() {
ListDetailPaymentTransactions(global.strtransidtrans);
global.doubbalanced = global.inttotalpayment - global.doubtranstotaltrans;
print(global.inttotalpayment - global.doubtranstotaltrans);
}
Currently I found this code:
Navigator.push(context, MaterialPageRoute(builder: (context) => CashPayment())).whenComplete(CountBalanced);
But the issue is when the user goes back to page 1 the functions are running all the time and wont stop.
I noticed it when I tried to print the result.
Is there any way to stop it? And is there any better way to reload all the function on page 1?

For stopping your function when navigation try disposing them
#override
void dispose() {
super.dispose();
}
To reloading the methods
#override
void didUpdateWidget(ClassName oldWidget) {
super.didUpdateWidget(oldWidget);
if (comparision) {
//your initState
}
}
For more information
https://docs.flutter.io/flutter/widgets/State/didUpdateWidget.html

Related

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 ListView resetting to top of list

I'm working on an app that gets the user to take a list of measurements. I use ListView to display a list of measurements. When the the user clicks on a list item it takes them to a new page, they enter the measurement, hit save and then in the save method I do Navigator.pop(context) back to the list. It all works but there is a usability problem.
If you tap a list tile and then use the app bar to go back it returns to the same scroll position in the ListView. If you enter some data then hit Save it returns to the top of the list. Even though I'm using Navigator.pop(context) in the save method. You can imagine returning to the top each time is pretty painful when the list of requirement measurements is quite long.
I guess is maybe its something to do with the fact that in the Save method I also update the model with which the list is built on so its kind of no longer the same list??
EDIT
I'm still not getting there and now I have an issue where the itemScrollController is not attached when I want to call it. Some code will hopefully help:
class ListContents extends StatefulWidget {
ListContents({
Key key,
}) : super(key: key);
#override
_ListContentsState createState() => _ListContentsState();
}
class _ListContentsState extends State<ListContents> {
ItemScrollController itemScrollController;
#override
void initState() {
super.initState();
itemScrollController = ItemScrollController();
}
I set up the scroll controller in init state. I set up a button to test this. I can run this when the page loads:
void jump(jumpIndex) {
itemScrollController.jumpTo(index: jumpIndex);
}
When I click that button it will jump to what ever index is passed.
I need to do this jumpTo when popping back from the previous page. I have this code in the list tiles. This loads an input page. - I was hoping to run the jumpTo method after it's popped.
onTapped: () async {
await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>
ChangeNotifierProvider<MeasureInstance>.value(
value: _instance,
child: InputOne(
index: index,
),
),
),
);
await _database.setInstance(_instance);
print(itemScrollController.isAttached);
itemScrollController.jumpTo(index: index);
},
When I pop back to the list page I see 'false' printed in the console and then an error that _jumpTo was called on null

Flutter Opening the keyboard trigger my Navigator.push() callback

In my app, I scan a barcode on the first page and call a second page when a valid barcode is scanned. On my second page, I have to edit some TextFields. I close the camera before calling the navigator and start it again when we close the second page using the .then() of the method Navigator.of(context).push().
The problem is as follows: when I click on a textfield, the keyboard appear and the .then() mentioned above is triggered. How do I do to not call it only when I'm back from the second page ?
if(result != null){
Navigator.push(scanned.context, MaterialPageRoute(builder: (BuildContext context) {
return InventoryItemSetQuantities(product: result, inventoryBloc: this, qrViewController: scanned.qrViewController);
})).then((value) => scanned.qrViewController.resumeCamera());
}
[EDIT]
Ok sorry, you needed to open the barcode scanner when the page popped, after you have filled out your textfield. In this case the easiest thing to do, is open the barcode scanner when the second page has been disposed (after popping). So do this:
#override
void dispose() {
super.dispose();
scanned.qrViewController.resumeCamera()
}
Make sure though to have a reference to the barcode scanner. You might have to pass into the second page as a constructor, thena ccess the reference to the scanner via the widget object, like so:
#override
void dispose() {
super.dispose();
widget.scanned.qrViewController.resumeCamera() //**scanned** is the passed barcode scanner from the first page
}
And in the original code make sure to remove the barcode scanner after then() callback. Do this:
if(result != null){
Navigator.push(scanned.context, MaterialPageRoute(builder: (BuildContext context) {
return InventoryItemSetQuantities(product: result, inventoryBloc: this, qrViewController: scanned.qrViewController);
}));
}

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.

How to wait for a method that is already being executed?

I'm developing a Flutter app which has some tabs inside, each of them depend on the database that is loaded on the first run. State is stored in a ScopedModel.
On every tab I have this code:
#override
void initState() {
super.initState();
loadData();
}
void loadData() async {
await MyModel.of(context).scopedLoadData();
_onCall = MyModel.of(context).onCall;
setState(() {
});
}
And this is the code snippet that matters for the ScopedModel:
Future<Null> scopedLoadData() async {
if (_isLoading) return;
_isLoading = true;
(...)
_isLoading = false;
}
If the user waits on the first tab for a few seconds everything is fine, since Database is loaded. However, if the user switches tabs right after app launch, the method scopedLoadData is still being executed so that I get a runtime error ("Unhandled Exception: NoSuchMethodError: The method 'ancestorWidgetOfExactType' was called on null.").
This exception happens because the scopedLoadData has not yet been completed. So I'm looking for a way to wait for a method that is still being executed.
Thanks!
Not sure without seeing your build method but I would start your build method with a guard clause.
if (_oncall == null) return Container(); // or Text("loading") or similar
use should be using a FutureBuilder on each tab to make sure the data is loaded before you try to build the widget... more code would be helpful
I solved the exception by getting rid of every:
setState(() { });
and implementing ScopedModelDescendant on every relevant tab on top of using notifyListeners() at the end of the database loading method.
This pulls the responsibility from the tabs for updating views and gives it to the scopedLoadData().