2

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.

3 Answers 3

3

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'),
        ),
      ),
    );
  }
}
Sign up to request clarification or add additional context in comments.

1 Comment

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!!!.
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),
      ),
    );
  },
);

5 Comments

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)."
@Idiaz997 above code contains twice the Navigator.of(context).pop(); . Just use once Also make sure that you are calling showDialog() within a StatefulWidget .
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.
@Idiaz997 above code shoud work . If not, could you provide your stateful widget code?
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.
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.

1 Comment

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.