How to correctly use ChangeNotifier
with SearchDelegate
?
I have something that looks like this:
class SearchNotifier with ChangeNotifier {
List<String> results;
Future<void> search(String query) async {
results = await API.search(query);
notifyListeners();
}
}
And in my SearchDelegate
:
Widget buildSuggestions(BuildContext context) {
final searchNotifier = Provider.of<SearchNotifier>(context);
searchNotifier.search(query);
...
}
When the results update, SearchNotifier
updates its listeners, SearchDelegate
is rebuild, buildSuggestions
is called and search
is called again, entering in a loop.
Is there a way of doing searchNotifier.search(query)
outside a build method? Maybe somehow I can add a listener to SearchDelegate
_queryTextController
?
I'm using provider to inject my SearchNotifier
, so wherever call search
we need to have access to the context.
Solution 1: Sergey
I made it differently, but I am also using the Provider in my app. So my custom search delegate looks like this:
class ExerciseSearchDelegate extends SearchDelegate {
@override
List<Widget> buildActions(BuildContext context) {
return [
IconButton(
icon: Icon(Icons.clear),
onPressed: () {
query = '';
},
),
];
}
@override
Widget buildLeading(BuildContext context) {
return IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
close(context, null);
},
);
}
@override
Widget buildResults(BuildContext context) {
// ExeciseList is a widget which accepts a query as optional parameter
return ExerciseList(
query: query,
);
}
@override
Widget buildSuggestions(BuildContext context) {
return Column();
}
@override
ThemeData appBarTheme(BuildContext context) {
assert(context != null);
final ThemeData theme = Theme.of(context);
assert(theme != null);
return theme;
}
}
and my ExerciseList looks like this:
class ExerciseList extends StatelessWidget {
final String query;
const ExerciseList({this.query, Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
final _model = Provider.of<GlobalModel>(context);
List<ExerciseModel> _results = query != null
? _model.exercises
.where((ex) => ex.name.toLowerCase().contains(query.toLowerCase()))
.toList()
: _model.exercises;
return ListView.builder(
itemCount: _results.length,
itemBuilder: (context, position) {
var exercise = _results[position];
return MultiProvider(
providers: [
ChangeNotifierProvider.value(notifier: exercise),
],
child: ExerciseListItem(),
);
},
);
}
}
Hope this helps. If something is not clear, please let me know. Thank you!
Solution 2: lgvaz
I solved the issue by returning the search results directly from the search
method and then using a FutureBuilder
.
class SearchNotifier with ChangeNotifier {
Future<List<String>> search(String query) async {
return await API.search(query);
}
}
Widget buildSuggestions(BuildContext context) {
final searchNotifier = Provider.of<SearchNotifier>(context);
results = searchNotifier.search(query);
// FutureBuilder on results
...
}
SearchNotifier
does not need to be a ChangeNotifier
anymore, since the origin of the issue was notifyListeners
.