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

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

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: CupertinoTabScaffold tab content does not refresh after closing PageRoute

Problem: CupertinoTabScaffold tab content does not refresh after closing new screen from CupertinoPageRoute
App:
There is a list of data in a Listview.
I open one data set and remove the favourite status (checked=false).
I close the data set and the listview should refresh (FutureBuilder to an SQL database).
Details:
I've got a Flutter App with two layouts (Material and Cupertino).
In each layout i have four tabs and the content widgets are the same.
On the Cupertino App every tab consists of a CupertinoPageScaffold.
When i press on an element it navigates to a new screen with CupertinoPageRoute.
After closing the CupertinoPageRoute the tab content is not refreshing.
The tab content is refreshing when i switch between the four tabs.
If one CupertinoPageRoute is open and i open a new one from here and close it again
then the content of the opened CupertinoPageScaffold is refreshing.
The data is refreshing without a callback etc., just by calling the future (i think).
Means there should be a problem with the CupertinoTabScaffold.
There is no problem on the Material App.
The tab contents of the Material App are refreshing when closing a MaterialPageRoute.
Question:
Is there a problem at this position?
Do you have the same issue or maybe a solution or workaround?
Code:
My code is not really representative so i will create a new clean project for you.
please confirm if code is necessary.
What i tried:
I tried a lot of solutions from the internet like callbacks, setState, .then, .whencomplete for the route.
nothing seems to work.
Thank you in advance.
EDIT 03.10.2021: the tabcontroller with SingleTickerProviderStateMixin reloads the open tab when i open or close an new screen. if i just use DefaultTabController Widget than it doesn't. So i have to edit the CupertinoTabController.
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
late TabController _tabController;
final CupertinoTabController _cuptabController = CupertinoTabController();
Future<void> _refreshdata() async {
downloadJSON1();
setState(() {
});
}
callback() {
_refreshdata();
}
#override
void initState() {
_refreshdata();
super.initState();
_tabController = TabController(vsync: this, length: 2, initialIndex: 0);
_cuptabController.index = widget.starttab;
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
...
}
I can suggest a few options:
Option 1:
change your FutureBuilder into a StreamBuilder.
FutureBuilder does not refresh automatically when you save the data which StreamBuilder does very well.
Option 2:
Use a setState but not on the context of the route you are closing, but rather on the context of the original page you want to refreh
setState(() { });
for the moment i solved it with a .then-callback from PageRoute. i just passed the callback to the deepest Widget/PageRoute. But if you have a solution/idea for the tabcontroller would be great.
Souce:
how to refresh state on Navigator.Pop or Push in flutter
Navigator.push(context,
CupertinoPageRoute(
builder: (BuildContext context) => MyWidget(
'myparameters'),),).then((value) => setState(() {callback();}));

Flutter provider and widget lifecycle

I have a simple flow:
a UserModel implementing ChangeProvider which wraps the state of the user (if it is logged in and utilities to log him in/out). In particular logout looks like:
void logout() {
user = null;
notifyListeners();
}
a UserPage widget with (among others):
class UserPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
// ... adding only relevant code
// a text with the user first letter of the email
Text(context.watch<UserModel>().user.email[0])
// a logout button with the following onPressed method
TextButton( \\ ...
onPressed: () {
context.read<UserModel>().logout();
Navigator.pop(context);
}
)
}
}
I was expecting that pressing logout and popping the UserPage widget will not let flutter rebuild it. However it is not the case and the notifyListeners() in logout method makes flutter rebuild the widget and trigger a NullPointerException (since the user is null and the email can't be accessed).
I could deal with it (checking if the user object is != null but I would like to understand why this happens).
Is it correct to assume pop destroys the widget? If not, how should I handle this case?
When a user is logged out I don't want to have in memory this widget nor deal with its existence. I would expect to create a UserPage when a user logs in and destroy it after its logout
When you call logout , watch in this line Text(context.watch<UserModel>().user.email[0]) will cause the widget it's in to rebuild.
You need to call logout in the call back of push the one you called to push this page. pop can also send values to inform about the conditions like success or failure.
So it would be something like this:
Navigator.pushNamed(context, some_route).then(value) {
context.read<UserModel>().logout();
}
value in the call back can be returned from pop like so Navigator.of(context).pop(true);
This will ensure that logout is only called after the widget has been popped.
If you don't like callbacks you can use async await pattern.

Flutter/Dart - Edit Page in PageView, then Refresh and Scroll back to Same Place?

Currently, I can submit edits to a single page in a PageView and then either Navigator.push to a newly created single edited page or Navigator.pop back to the original Pageview containing the unedited page.
But I'd prefer to pop back to the the same place in an updated/refreshed Pageview. I was thinking I could do this on the original PageView page:
Navigator.pushReplacement(context,new MaterialPageRoute(
builder: (BuildContext context) => EditPage()),);
But after editing, how can I pop back to a refreshed PageView which is scrolled to the now updated original page? Or is there a better way? Someone mentioned keys, but I've not yet learned to use them.
The question deals with the concept of Reactive App-State. The correct way to handle this is through having an app state management solution like Bloc or Redux.
Explanation: The app state takes care of the data which you are editing. the EditPage just tells the store(App-State container) to edit that data and the framework takes care of the data that should be updated in the PageView.
as a temporary solution you can use an async call to Navigation.push() and refresh the PageView State once the EditPage comes back. you can also use an overloaded version of pop() to return a success condition which aids for a conditional setState().
Do you know that Navigator.pushReplacement(...) returns a Future<T> which completes when you finally return to original context ?
So how are you going to utilize this fact ?
Lets say you want to update a String of the original page :
String itWillBeUpdated="old value";
#override
Widget build(BuildContext ctx)
{
.
.
.
onPressesed:() async {
itWillBeUpdated= await Navigator.pushReplacement(context,new MaterialPageRoute(
builder: (BuildContext context) => EditPage()),);
setState((){});
},
}
On your editing page , you can define Navigator.pop(...) like this :
Navigator.pop<String>(context, "new string");
by doing this , you can provide any data back to the original page and by calling setState((){}) , your page will reflect the changes
This isn't ideal, but works somewhat. First I created a provider class and added the following;
class AudioWidgetProvider with ChangeNotifier {
int refreshIndex;
setRefreshIndex (ri) {
refreshIndex = ri;
return refreshIndex;
}
}
Then in my PageView Builder on the first page, I did this;
Widget build(context) {
var audioWidgetProvider = Provider.of<AudioWidgetProvider>(context);
return
PreloadPageView.builder(
controller: PreloadPageController(initialPage: audioWidgetProvider.refreshIndex?? 0),
Then to get to the EditPage (2nd screen) I did this;
onPressed: () async {
audioWidgetProvider.setRefreshIndex(currentIndex);
Navigator.pushReplacement(context,new MaterialPageRoute(builder: (BuildContext context) => EditPage()),); }
And finally I did this to return to a reloaded PageView scrolled to the edited page;
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) =>HomePage()));
The only problem now is that the PageView list comes from a PHP/Mysql query and I'm not sure what to do if new items are added to the list from the Mysql database. This means the currentIndex will be wrong. But I guess that's the topic of another question.

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

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