31

When I tried to call a method inside of State dispose method like below.

  @override
  void dispose() {
    Provider.of<AppProvider>(context, listen: false).close();
    super.dispose();
  }

I got this.

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.

When the exception was thrown, this was the stack
#0      Element._debugCheckStateIsActiveForAncestorLookup.<anonymous closure> 
package:flutter/…/widgets/framework.dart:3508
#1      Element._debugCheckStateIsActiveForAncestorLookup 
package:flutter/…/widgets/framework.dart:3522
#2      Element.getElementForInheritedWidgetOfExactType 
package:flutter/…/widgets/framework.dart:3588
#3      Provider.of 
package:provider/src/provider.dart:219
#4      _MySecondPageState.dispose 
package:test_space/main.dart:138
...

This is my example AppProvider

class AppProvider {
  close() {}
}

and I wrapped MaterialApp with AppProvider

return Provider<AppProvider>(
      create: (_) => AppProvider(),
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: HomeScreen(),
        },
      ),
    );

I'm not sure how to do this 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. I'm currently using provider: ^4.0.2.

I used to do it the same, using Provider.of(context) inside of dispose(), without this exception before. Is this because new flutter version? Also, I realized there is the deactivate() method, should I use deactivate() method instead of dispose() method?

[UPDATED]

  AppProvider _appProvider;
  @override
  void didChangeDependencies() {
    _appProvider = Provider.of<AppProvider>(context, listen: false);
    super.didChangeDependencies();
  }

  @override
  void dispose() {
    _appProvider.close();
    super.dispose();
  }

I could also use deactivate in a certain case

  @override
  void deactivate() {
    Provider.of<AppProvider>(context, listen: false)
        .close();
    super.deactivate();
  }
user12208004
  • 1,704
  • 3
  • 15
  • 37

4 Answers4

6

It seems like you are trying to close something which is defined in your AppProvider class. If AppProvider class is extending ChangeNotifier, the change notifier class provides dispose method, you can override it and then call the close function inside the AppProvider class only.

Gautam Kumar
  • 351
  • 4
  • 7
3

As far as I remember this behaviour is long ago, I usually use after_layout to solve it like:

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

class _MyWidgetState extends State<MyWidget> with AfterLayoutMixin {
  AppProvider _appProvider;

  @override
  void afterFirstLayout(BuildContext context) {
    _appProvider = Provider.of<AppProvider>(context, listen: false);
  }

  @override
  void dispose() {
    _appProvider.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      // ...
    );
  }
}
Crizant
  • 1,571
  • 2
  • 10
  • 14
2

If you need to dispose something before leaving context, you can dispose it in willPopScope

 WillPopScope(
    onWillPop: () {
       context.read<AppProvider().myController?.dispose();
       Navigator.pop(context);
       return Future.value(false);
    },
    child: ...,
 )
  • 1
    It is not a reliable solution as ```onWillPop``` doesnt gets called with Navigator.pushReplacement() or with bottom navigation bars. – sh_ark Dec 23 '21 at 12:27
  • Problem with this solution is that it disables other navigation like tap out side modal or back button for example. – Oliver Dixon Apr 10 '22 at 09:26
0

You should consider using Provider.dispose callback, which is called right when the provider is unmounted from the widget tree:

return Provider<AppProvider>(
      create: (_) => AppProvider(),
      dispose: (_, appProvider) => appProvider.close()
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: HomeScreen(),
        },
      ),
mip
  • 8,355
  • 6
  • 53
  • 72