In my flutter application I have a button which starts an asynchronous network operation and displays a dialog while waiting for it to complete. Once completed, the dialog is popped from the navigation, but I am running into concurrency issues. Here's my code:
ElevatedButton(
onPressed: () async {
showDialog(
context: context,
builder: (context) => Center(child: CircularProgressIndicator()),
);
await asyncNetworkOperation();
Navigator.pop(context);
},
child: Text("Press here");
)
If the user taps on the Android back button while the network operation is in progress, the dialog gets popped ahead of time. And then, once the network operation completes, another Navigator.pop(context) is issued which pushes the navigation back one extra step.
What is the best way to avoid Navigator.pop from executing if the user already popped the dialog? I know I could prevent the back button from working altogether with the WillPopScope
widget, but I would like the user to have the ability to abort the operation should it take too long.
TLDR: How to prevent a Navigator.pop()
in an async frunction from popping a route which has already been popped?
CodePudding user response:
You could check if the dialog is active or not. Only go back if there is active dialog
Example:
_isOpen = true;
ElevatedButton(
onPressed: () async {
showDialog(
context: context,
builder: (context) => Center(child: CircularProgressIndicator()),
).then((_) => _isOpen = false);
await asyncNetworkOperation();
if(_isOpen){
Navigator.pop(context);
}
},
child: Text("Press here");
)
CodePudding user response:
A versatile approach (and probably recommended) is using the mounted property, which would require you to use a StatefulWidget
:
class MyDialog extends StatefulWidget {
const MyDialog({Key? key}) : super(key: key);
@override
State<MyDialog> createState() => _MyDialogState();
}
class _MyDialogState extends State<MyDialog> {
@override
Widget build(BuildContext context) {
return AlertDialog(
title: const Text('AlertDialog Title'),
content: const Text('AlertDialog description'),
actions: <Widget>[
TextButton(
onPressed: () async {
await Future.delayed(const Duration(seconds: 5));
if (!mounted) {
return;
}
Navigator.pop(context, 'Cancel');
},
child: const Text('Cancel'),
),
],
);
}
}
After creating a State
object and before calling initState
, the framework "mounts" the State object by associating it with a BuildContext
. The State
object remains mounted until the framework calls dispose
, after which time the framework will never ask the State
object to build again, therefore you can check the mounted
property before safely using the context object, for other purposes other than just popping the navigator as well.
See: