为什么我会在 PopupMenuItem 上看到“查找已停用小部件的祖先是不安全的”?

Mag*_*son 7 dart flutter

我有一个 Flutter 页面/小部件,带有一个搜索框和一个从flutter_bloc BLOC填充的 ListView 。
当输入文本时,我调用一个 BLOC 方法来搜索结果,并将结果呈现在一个 ListView 中。
在此处输入图片说明 当我点击项目PopupMenuItem内部时,ListTile我得到:

在此处输入图片说明

查找停用的小部件的祖先是不安全的。此时小部件的元素树的状态不再稳定。要在其 dispose() 方法中安全地引用小部件的祖先,请通过在小部件的 didChangeDepencies() 方法中调用 DependOnInheritedWidgetOfExactTpye() 来保存对祖先的引用。

如果我用stations变量的简单静态列表初始化替换 BLOC“舞蹈” ,它工作正常。

我完全不知道该怎么办?有人可以给我一些指点吗?

编辑:这是一个重现示例(仅限 Android):https : //github.com/MagnusJohansson/bloc_test
单击应用程序中的搜索图标,输入“test”作为搜索词。单击三点状菜单以触发错误(可能需要单击两次)。如果不执行 sqlite 查询,而是返回一个静态列表(如文件 /bloc/searchpresets/search_presets_bloc.dart 第 55 行中的注释所示),它工作正常。

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();
        }),
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

Joã*_*res 13

这是一个问题contexts。这种情况经常发生在 和 的情况下Dialogs,其PopMenu行为有点类似于对话框,但是使用对话框,您可以传递context所需的值,并使用 aGlobalKey和访问 的context来解决此问题Scaffold。不幸的是这里这是不可能的。

尽管如此,PopMenuButton 上有一个标志似乎可以解决您的问题。captureInheritedThemes。文档指出:“如果为 true(默认值),则菜单将使用 InheritedThemes 的副本进行包装,例如 Theme 和 PopupMenuTheme,它们是在显示菜单的 BuildContext 上方定义的”。这似乎是我们的问题,上下文的继承。

在您的search_presets_widget.dart文件中的方法PopupMenuButton内部_buildListView,添加以下行:

PopupMenuButton(
  captureInheritedThemes: false, // Add this line
  ...
Run Code Online (Sandbox Code Playgroud)