Flutter Navigator use in async function issue - flutter

I'm writing an app where the login screen asks the user for various permissions and to login using Facebook.
Once all the permissions have been requested, the app moves to the next screen to ask further information and then returns to the calling screen to complete its tasks.
It seems that Navigator.push doesn't work as expected in async functions however.
void function() async {
<...do something 1...>
Navigator.pushNamed(context, screenName);
<...do something 2...>
}
Expected behaviour
would be that <...do something 1...> would run, then Navigator would call the next screen, and once Navigator.pop(context); was called by the second screen, the app would return to the first screen and execute <...do something 2...>. This is the way it works in every other screen in the app.
Actual behaviour
<...do something 1...> called, then <...do something 2...> and then at some point the Navigator is triggered.
This is causing me to have to do all sorts to try to block the flow of the application through this, but it seems very peculiar behaviour.
Has anyone else experienced this, or can give me a way around it? This doesn't even involve the await parts of the function, and to say it's driving me nuts is somewhat of an understatement.
Any help much appreciated!

I believe Navigator.pushNamed returns a Future. As such, perhaps if you just add the await like so:
void function() async {
<...do something 1...>
await Navigator.pushNamed(context, screenName);
<...do something 2...>
}
It can solve the issue, although I did not try it.

You can use .then() function to achieve what you want, example,
(Using email and password SignIn.)
onPressed: () {
authHandler.handleSignInEmail(emailController.text, passwordController.text)
.then((FirebaseUser user) {
Navigator.push(context, new MaterialPageRoute(builder: (context) => new HomePage()));
}).catchError((e) => print(e));
}

Related

Why is my function preventing the dialog dismiss/pop in Flutter?

I am trying to execute a function after a dialog is dismissed/popped. I read this article How to run code after showDialog is dismissed in Flutter? and tried to do it as recommended but it wouldn't work for me.
This is how I call my dialog:
Future<void> onDeleteEventData(BuildContext context) async {
final title = context.messages.settings.offline.deleteEventData;
final subTitle = context.messages.settings.offline.deleteEventDataDesc;
final res = await showDeleteDialog(context,
title: title,
subTitle: subTitle);
if (res == true){
context.read<EventDownloadTileController>().deleteEventRelatedData();
}
}
The showDeleteDialog function just calls a custom Dialog which is basically just the Flutter Dialog with some style changes.
Future<bool?> showDeleteDialog(BuildContext context,
{required String title, String? subTitle}) async {
return await showDialog(
context: context,
builder: (_) => DeleteDialog(title: title,subTitle: subTitle,)
);
}
In the dialog I press on a button and do this:
onPressed: () => Navigator.of(context).pop(true),
So looking at the first function I wait for my res which evaluates to true. At this point I thought the dialog should be popped. But it is not.
The problem is this call:
context.read().deleteEventRelatedData();
Because when I replace this call with e.g. Future.delayed(duration(seconds:5)); the dialog pops right away as expected.
This is the function:
Future<void> deleteEventRelatedData() async {
_ticketLoader.stop();
_ticketStorage.deleteScanTicketsForEvent(event.eventId);
_eventStorage.deleteEventPermissions(event.eventId);
_eventStorage.deleteEventData(event.eventId);
_ticketStorage.deleteCachedTicketsForEvent(event.eventId);
_ticketStorage.deleteCachedUnknownTicketsForEvent(event.eventId);
_ticketLoader.updateLastSync(null);
_ticketLoader.reset();
checkLocalStatus();
}
A function with some async and synchronous functions. The execution takes up to 3 seconds which is the time it takes to dismiss/pop my dialog. But I want to pop the dialog right away and let it work in the back. What could my function possibly do for this behavior?
Thanks in advance
The dialog window isn't going to disappear until the app can manage to do a rebuild. If your function call takes a while, it could be hogging the main thread until it's complete, disallowing other code (including widget code) from running.
Try wrapping your function call in a microtask so it doesn't run until the next available task window which will give the app time to clean up the dialog window:
await Future.microtask(deleteEventRelatedData);
It's also worth mentioning the body of the deleteEventRelatedData is marked as async but it never awaits anything. That means all of the synchronous calls can happen in a sequence that wasn't intended and the asynchronous calls won't get executed until a later time and in no guaranteed order.

Navigator.pop does not closing showModalBottomSheet when the StartWidget is displayed again

I have the following route:
StartWidget -> showModalBottomSheet -> NextWidget -> NextWidget: Doing some work and calls finally Navigator.pop and showing MainWidget but with open showModalBottomSheet.
Which navigator method do i have to use, in order to display only the StartWidget?
Thanks
Navigator.pushNamed returns a Future. As such, perhaps if you just add the await like so
void goToNextPage() async {
<...do something 1...>
await Navigator.pushNamed(context, screenName);
<...do something 2...> //open bottom sheet
}
It can solve the issue.
If I understood your question correctly, I think you can use the popUntil method to get back to the StartWidget, the documentation is here:
https://api.flutter.dev/flutter/widgets/Navigator/popUntil.html

Don't assign to void

I tried to figure out the reason, but it seems there is only one issue linked to github, it keeps displaying this warning, plz let me know the reason, thanks a lot!
onPressed: () async {
print('now trying to purchase');
_purchaserInfo = await Purchases.purchasePackage(widget.package);
print('purchase completed');
appData.isGoldWorm = _purchaserInfo.entitlements.all["all_features"].isActive;
print('is user pro? ${appData.isGoldWorm}');
}
Without seeing the entire code and warning it is hard. I see you are defining the _purchaseInfo with the await but you aren't calling it so it may seem to the function that you are using async but not await, and so you cant assign it correctly.

Firebase Cloud Messaging onLaunch callback

My app structure is a little bit mess, but I have to add this patch first and then I'll restructure the entire logic. The thing is I first check if there's a firebase user, then if there is one I use StreamBuilder to get the current user profile from Firestore, then I have the _firebaseMessaging.configure method because onLaunch and onResume I use this callback:
void _navigateToGestorResevas(Map<String, dynamic> message, User currentUser) {
Navigator.push(context,
MaterialPageRoute(builder: (context) =>
GestorScreen(user: currentUser)));
}
Because I need to send the User to this screen where he fetch the message from firebase.
onResume this works fine, but onLaunch it goes to the screen and fetch the data but there are like 20 seconds where there are some kind of glitch. It switch like 20-30 times between two states where I have and no have snapshot data in this _initState func:
final snapshot = await _dbRef.child('mensajes').child(widget.user.id).once();
if (snapshot.value != null) {
setState(() {
hayMensajes = true;
});
final data = snapshot.value;
for (var entry in data.entries) {
Message message = Message.fromJson(entry.value);
setState(() {
message.add(message);
});
}
} else {
setState(() {
hayMensajes = false;
});
}
Anyone have an idea what am I doing wrong?
If I am not mistaken, there are some active issues about FCM onLaunch callback with flutter. Some of them are still not fixed. One of the problems most people had to face was that onLaunch callback being called multiple times. I don't know why it happened, but as in your case, you can possibly get rid of the issue by some temporary fixes.
If the same screen is getting pushed over and over again, and glitching, you can pop the stack until it reaches the one you meant to open and set a condition to push navigator only if the new route is different from the old one. Using the named routes,
Navigator.popUntil(context, ModalRoute.withName(routeName));
if (ModalRoute.of(context).settings.name != routeName) {
Navigator.pushNamed(context, routeName);
}
I am not sure if that was the problem you asked, but I hope at least my answer helps somehow.

How to use Future<bool> in if statement condition?

In Flutter, am using "url_launcher" package which uses Future<bool> to check if the app can launch other apps. Within my code I have phone numbers listed, and I want to place an Icon for WhatsApp only if it is installed on the user's device.
bool hasWhatsApp() {
var whatsExists;
canLaunch('https://api.whatsapp.com/').then((val) => whatsExists = val);
return whatsExists;
}
am using this function to check inside an if statement to show the icon on the screen or not, however, it keeps returning 'null'.
if (phoneNumber.substring(0, 1) != '2' && hasWhatsApp())
IconButton(
icon: Image.asset('assets/icons/whatsapp.png'),
iconSize: Theme.of(context).iconTheme.size,
onPressed: () async {
await launch(whatsUrl);
},
),
how can I fix this please?
What you are trying to do cannot work.
Your function hasWhatsApp is synchronous, so it returns a value immediately when you call it.
Inside that function, you start an asynchronous computation, and when that computation finishes (at a later time), it overwrites a local variable. The function has long returned by then.
There is no way you can immediately return a value which is not available until later. It's simply not there.
So, you need to await the future so you can delay making the decision until the value is available.
For example, change your if to:
if (phoneNumber.substring(0, 1) != '2' &&
await canLaunch('https://api.whatsapp.com/')) {
...
}
That does mean that you have to make the function containing that if asynchronous.
I'm not a Flutter expert, I guess you might need to use a FutureBuilder.
There is no work-around for asynchrony.
You can await canLaunch method and convert your function as async. Since you make your function async, your return type needs to be Future Try this:
Future<bool> hasWhatsapp() async {
return await canLaunch('https://api.whatsapp.com/');
}
And if you use https://api.whatsapp.com/ as url it will always return true because it can launch it via browser. If you want to check if app is installed I guess you need to use canLaunch('whatsapp://') instead. Check https://stackoverflow.com/a/50672986/12709039 this answer for more