Home > Blockchain >  How to prevent a Navigator.pop() in an async function from popping a route which doesn't exist
How to prevent a Navigator.pop() in an async function from popping a route which doesn't exist

Time:06-15

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:
  1. https://dart-lang.github.io/linter/lints/use_build_context_synchronously.html
  2. https://api.flutter.dev/flutter/widgets/State/mounted.html
  • Related