Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do I get "Looking up a deactivated widget's ancestor is unsafe" on my PopupMenuItem?

Tags:

flutter

dart

I have a Flutter page/widget, with a search box and a ListView populated from a flutter_bloc BLOC.
When text is entered, I call a BLOC method to search for results, and the result is presented in a ListView.
enter image description here When I click on the PopupMenuItem inside the ListTile item I get:

enter image description here

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 dependOnInheritedWidgetOfExactTpye() in the widget's didChangeDepencies() method.

If I replace the BLOC "dance" with a simple static list initialization of the stations variable, it works fine.

I am completely lost on what to do? Can someone please give me some pointers?

Edit: Here's a repro sample (Android only): https://github.com/MagnusJohansson/bloc_test
Click on the search icon in the app, enter "test" as the search term. Click on the 3-dot menu to trigger the error (might have to click twice). If not doing the sqlite query, and instead returning a static list (as seen in the comment in file /bloc/searchpresets/search_presets_bloc.dart line 55) it works fine.

class SearchPresetsPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _SearchPresetsPageState();
}

final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey2 =
    new GlobalKey<RefreshIndicatorState>();

final GlobalKey<ScaffoldState> scaffoldKey2 = new GlobalKey<ScaffoldState>();

final snackBar = SnackBar(
  content: Text('Added as a favorite'),
  action: SnackBarAction(
    label: 'Undo',
    onPressed: () {},
  ),
);

class _SearchPresetsPageState extends State<SearchPresetsPage> {
  final TextEditingController _filter = new TextEditingController();
  List<Station> stations = [];

  _textInputSearch() {
    if (_filter.text.length >= 2) {
      BlocProvider.of<SearchPresetsBloc>(context)
          .add(SearchPresets(_filter.text));
    }
  }

  @override
  void initState() {
    _filter.addListener(_textInputSearch);
    super.initState();
  }

  Widget buildInitialStationsInput(BuildContext context, String message) {
    return Column(children: <Widget>[
      Center(child: Text(message)),
    ]);
  }

  Widget buildLoading() {
    return Center(
      child: LinearProgressIndicator(
        value: null,
      ),
    );
  }

  Widget _buildListView() {
    return RefreshIndicator(
      key: _refreshIndicatorKey2,
      child: Column(
        children: <Widget>[
          Container(
            color: Colors.grey[300],
            alignment: Alignment.topLeft,
            child: Padding(
              padding: const EdgeInsets.all(4.0),
              child: Text(
                "${stations.length} Stations:",
              ),
            ),
          ),
          Expanded(
            child: ListView.builder(
              itemCount: stations != null ? stations.length : 0,
              itemBuilder: (context, index) {
                return Card(
                  child: ListTile(
                    dense: true,
                    title: Text(stations[index].name),
                    leading: IconButton(
                      alignment: Alignment.center,
                      icon: Icon(Icons.play_arrow),
                      onPressed: () async {
                      },
                    ),
                    trailing: PopupMenuButton(
                      onSelected: (value) async {
                      },
                      itemBuilder: (context) {
                        return [
                          PopupMenuItem(
                            value: 1,
                            child: Text("Add to favorites"),
                          ),
                        ];
                      },
                    ),
                  ),
                );
              },
            ),
          ),
        ],
      ),
      onRefresh: () async {
        BlocProvider.of<SearchPresetsBloc>(context)
            .add(SearchPresets(_filter.text));
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return BlocListener<SearchPresetsBloc, SearchPresetsState>(
      listener: (BuildContext context, SearchPresetsState state) {},
      child: Scaffold(
        key: scaffoldKey2,
        appBar: AppBar(
          actions: <Widget>[],
          title: Container(
            color: Colors.white,
            child: TextField(
              controller: _filter,
              decoration: new InputDecoration(
                  filled: true,
                  suffixIcon: IconButton(
                    icon: Icon(Icons.clear),
                    onPressed: () {
                      _filter.clear();
                      setState(() {
                        stations = [];
                        BlocProvider.of<SearchPresetsBloc>(context)
                            .add(ClearSearch());
                      });
                    },
                  ),
                  prefixIcon: new Icon(Icons.search),
                  hintText: 'Search...'),
            ),
          ),
        ),
        body: BlocBuilder<SearchPresetsBloc, SearchPresetsState>(
            builder: (BuildContext context, SearchPresetsState state) {
          if (state is InitialSearchPresetsState) {
            return buildInitialStationsInput(context,
                "Search for stations in the search box above (at least 2 characters)");
          }
          if (state is SearchPresetsLoading) {
            return buildLoading();
          }
          if (state is SearchPresetsLoaded) {
            stations = state.stations;
            return _buildListView();
          }
          if (state is SearchPresetsError) {
            return buildInitialStationsInput(
                context, state.exception.toString());
          }
          return Container();
        }),
      ),
    );
  }
}
like image 439
Magnus Johansson Avatar asked Jan 07 '20 21:01

Magnus Johansson


1 Answers

This is an issue of contexts. It happens often with Dialogs, and the PopMenu behaves a bit similar to a dialog, but with a dialog you can pass the context you want and solve this problem with a GlobalKey and accessing the context of the Scaffold. Unfortunately here this is not possible.

Nonetheless, there is a flag on the PopMenuButton that seems to solve your issue. captureInheritedThemes. The documentation states: "If true (the default) then the menu will be wrapped with copies of the InheritedThemes, like Theme and PopupMenuTheme, which are defined above the BuildContext where the menu is shown". That seems to be our problem, the inheritance of context.

In your search_presets_widget.dart file, in your PopupMenuButton inside the _buildListView method, add this line:

PopupMenuButton(
  captureInheritedThemes: false, // Add this line
  ...
like image 94
João Soares Avatar answered Nov 03 '22 10:11

João Soares