0

I'm working on a website made in Flutter. I want that after some action is completed successfully, a dialog is displayed, it closes automatically, and it goes back to the previous page. My problem is that I can't make the following error disappear.

Error: Looking up a deactivated widget's ancestor is unsafe.

This is my code.

showDialog<void>(
        context: context,
        barrierDismissible: false, // user must tap button!
        builder: (BuildContext buildContext) {
          Timer(const Duration(milliseconds: 2000), () {
            Navigator.of(buildContext).pop();
            context.router.navigateBack();
          });
          return AlertDialog(
            title: const Text(AppGlobals.invalidFieldsText),
            content: const SingleChildScrollView(
                child: Text(AppGlobals.invalidFieldsDescriptionText)),
          );
        },
      );

If instead of closing the dialog automatically, I use a button to trigger, the error disappears.

showDialog<void>(
        context: context,
        barrierDismissible: false, // user must tap button!
        builder: (BuildContext buildContext) {
          return AlertDialog(
            title: const Text(AppGlobals.invalidFieldsText),
            content: const SingleChildScrollView(
                child: Text(AppGlobals.invalidFieldsDescriptionText)),
            actions: <Widget>[
              TextButton(
                child: const Text(AppGlobals.closeText),
                onPressed: () {
                  Navigator.of(buildContext).pop();
                  context.navigateBack();
                },
              ),
            ],
          );
        },
      );

I tried to find the solution here, here, here, here, and here but I couldn't solve the problem. please help.

ldiaz997
  • 128
  • 8

3 Answers3

1

It is because the widget tree has already been disposed of when the dialogue is supposed to be closed automatically after a predetermined length of time, which prevents Flutter from going back to the previous page.

Hence you can consider using a StatefulWidget to display the dialog and use the dispose function to stop the timer when the widget is removed.

Example:

class MyPage extends StatefulWidget {
  @override
  _MyPageState createState() => _MyPageState();
}

class _MyPageState extends State<MyPage> {
  Timer? _timer;

  void _showDialog() {
    showDialog<void>(
      context: context,
      barrierDismissible: false,
      builder: (BuildContext context) {
        _timer = Timer(const Duration(seconds: 2), () {
          Navigator.of(context).pop();
          Navigator.of(context).pop(); // navigate back
        });
        return AlertDialog(
          title: const Text('Dialog'),
          content: const Text('Dialog content'),
        );
      },
    );
  }

  @override
  void dispose() {
    _timer?.cancel(); // cancel timer when widget is removed
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: ElevatedButton(
          onPressed: _showDialog,
          child: const Text('Show Dialog'),
        ),
      ),
    );
  }
}
krishnaacharyaa
  • 14,953
  • 4
  • 49
  • 88
  • I found the solution to my problem with the help of your answer. I was already calling the `showDialog` method inside a `StatefulWidget`. Instead of using the `.pop` method twice, the second time I replace it with the `navigateBack` method, because I'm using `Nested Navigation`. The main change was to declare the `Timer` variable separately, and dispose it when the view is removed. Thank you so much!!!. – ldiaz997 Mar 08 '23 at 15:53
0

You might use Future.delayed() method to close the dialog automatically and Navigator.of(context).pop() to go back to the previous page.

// Show dialog
showDialog(
  context: context,
  barrierDismissible: false,
  builder: (BuildContext buildContext) {
    Future.delayed(const Duration(seconds: 2), () {
      Navigator.of(buildContext).pop(); // close the dialog
      Navigator.of(context).pop(); // navigate back to the previous page
    });
    return AlertDialog(
      title: const Text(AppGlobals.invalidFieldsText),
      content: const SingleChildScrollView(
        child: Text(AppGlobals.invalidFieldsDescriptionText),
      ),
    );
  },
);
gretal
  • 1,092
  • 6
  • 14
  • It does not work. Now i have the error "Error: This widget has been unmounted, so the State no longer has a context (and should be considered defunct)." – ldiaz997 Mar 08 '23 at 01:04
  • @Idiaz997 above code contains twice the `Navigator.of(context).pop(); `. Just use once Also make sure that you are calling showDialog() within a StatefulWidget . – gretal Mar 08 '23 at 01:23
  • If I only use one, the error disappears, but I don't go back to the previous page, the page from which the dialog was displayed remains. – ldiaz997 Mar 08 '23 at 01:34
  • @Idiaz997 above code shoud work . If not, could you provide your stateful widget code? – gretal Mar 08 '23 at 01:39
  • As you mentioned, the dialog is called within `StatefulWidget`. With the help of @BouncyBits's answer, I found the solution. it was to declare the `Timer` variable separately, and dispose it when the view is removed, as his answer shows. – ldiaz997 Mar 08 '23 at 15:44
0

You should use Navigator.popUntil(context, ModalRoute.withName('/')). Replace the '/' with your route name & voila!

Be careful, when you were calling this Navigator.of(buildContext).pop(), that passes the build context of the dialog builder; Not the root app context.

Key difference is using the default lowercase context which always refers to the app's main context. You were also trying to navigate again after your first .pop() which isn't possible because the widget (dialog) will close & will no longer be mounted.

  • I can't use popUntil because the view from which the dialog is fired is built from different views. So I wouldn't know which route name to use. – ldiaz997 Mar 08 '23 at 15:37