Mussadiq Tariq
Mussadiq Tariq

Reputation: 524

Looking up a deactivated widget's ancestor is unsafe. Navigator.of(context).pushAndRemoveUntil

navigator.pushandremoveuntil is working fine but an exception is thrown: This statement is executed from a class that extends ChangeNotifier (provider).

The following assertion was thrown while finalizing the widget tree: Looking up a deactivated widget's ancestor is unsafe.

At this point the state of the widget's element tree is no longer stable.

To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.

Navigator.of(context).pushAndRemoveUntil(MaterialPageRoute(builder: (_) {return EmailAuthVC();}), (Route<dynamic> route) => false);

Upvotes: 13

Views: 36461

Answers (3)

Shishir Rijal
Shishir Rijal

Reputation: 183

Just a few hours ago I also faced the same problem where I was getting the error exactly the same as you mentioned in your problem. When I tried to know what exactly is going wrong even on calling a simple navigator, I came to know that I was calling the context of a widget which is already dispose. Exactly what happened was, I was building a quiz app where the user was to be redirected to the result screen when time is over. So I was calling the navigator as

Future.delayed(Duration(seconds: quiz.quizDuration)).then((value) {
    // go to result screen when time is over
    Navigator.pushReplacement(context,
        MaterialPageRoute(builder: (context) => const ResultScreen()));
});

But the user would also be redirected to the result screen if he answers all questions before time. So this widget would be already disposed and our delayed function will complete after the time and try to use the context of a disposed widget.. So that's the error.

I solved it by checking if the state persists. If widget is still there dispose it, if it is already disposed do nothing.

Future.delayed(Duration(seconds: quiz.quizDuration)).then((value) {
  // check if is mounted
  if (mounted) {
    // if it is mounted then go to result screen, time is off bro..
    Navigator.pushReplacement(context,
        MaterialPageRoute(builder: (context) => const ResultScreen()));
  }
});

This is how I solved my problem.. So I guess you can also solve your problem by putting your navigator code inside

if(mounted) 
{
// navigator.... 
}

Upvotes: 5

Andri Geir Arnarson
Andri Geir Arnarson

Reputation: 1

I had a similar problem caused by the fact that I forgot to call timer.cancel() within a Timer.periodic() function.

Upvotes: 0

Lulupointu
Lulupointu

Reputation: 3594

What is the error?

As the error explains, you are trying to use dependOnInheritedWidgetOfExactType in the dispose method.

What this really means is that the context you are using is no longer part of the widget tree (because its state as been disposed) and therefore you cannot use it to call dependOnInheritedWidgetOfExactType.

But where are you using dependOnInheritedWidgetOfExactType? In Navigator.of(context). If you check its source code:

static NavigatorState of(
  BuildContext context, {
  bool rootNavigator = false,
}) {
  // Handles the case where the input context is a navigator element.
  NavigatorState? navigator;
  if (context is StatefulElement && context.state is NavigatorState) {
    navigator = context.state as NavigatorState;
  }
  if (rootNavigator) {
    navigator = context.findRootAncestorStateOfType<NavigatorState>() ?? navigator;
  } else {
    navigator = navigator ?? context.findAncestorStateOfType<NavigatorState>();
  }
  ...
  return navigator!;
}

How to solve this?

You have to use the context to get the Navigator before the dispose method. As explained in the error, you should create a reference to the object (in the didChangeDependencies method for example) and use it latter.

Here is a concrete example:

class MyWidget extends StatefulWidget {
  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  // The reference to the navigator
  late NavigatorState _navigator;

  @override
  void didChangeDependencies() {
    _navigator = Navigator.of(context);
    super.didChangeDependencies();
  }

  @override
  void dispose() {
    _navigator.pushAndRemoveUntil(..., (route) => ...);
    super.dispose();
  }
  @override
  Widget build(BuildContext context) {
    return ...;
  }
}

Why not use the initState method rather than didChangeDependencies? Because, as much as the context is not longer valid in dispose, the context is not yet valid in initState because the widget has not yet been inserted in the widget tree.

Upvotes: 42

Related Questions