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.
When I click on the PopupMenuItem
inside the ListTile
item I get:
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();
}),
),
);
}
}
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
...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With