Reputation: 8516
I have a large, ~5K items list.
What I wanted to do was to write a search view for these items.
I tried the following -
List<String> items;
String query;
ListView.builder(
itemBuilder: (context, index) {
for (int i = index; i < items.length; i++) {
var item = items[i];
if (item.contains(query)) {
return ItemTile(item);
}
}
}
This renders and searches for elements efficiently, but the problem is that it ends up repeating the last item in the list infinitely.
I guess that's because I wasn't providing an itemCount
.
So I tried to keep track of the number of filtered items myself, inside a Stateful
Widget.
var _count = 1;
_queryController.addListener(() {
setState(() => _count = 1);
});
ListView.builder(
itemBuilder: (context, index) {
for (int i = index; i < items.length; i++) {
var item = items[i];
if (item.contains(query)) {
setState(() => _count += 1);
return ItemTile(item);
}
}
itemCount: _count;
}
But then I receive an error, saying - setState() or markNeedsBuild() called during build.
What would be the right way to do this, without compltely searching through items for each query?
(I was aiming for a search-as-you-type UX)
Upvotes: 0
Views: 4366
Reputation: 8516
Here is a possible way to add debounce :-
var _controller = TextEditingController();
List<dynamic> _filtered;
@override
void initState() {
_filtered = widget.items;
_controller.addListener(() {
var query = _controller.text;
Future.delayed(Duration(milliseconds: 250), () {
if (!mounted) {
return;
}
if (_controller.text == query) {
setState(() {
_filtered = widget.items
.where((item) => item.contains(query)).toList())
.toList();
});
}
});
});
super.initState();
}
...
ListView.builder(
itemBuilder: (context, index) => ItemTile(_filtered[index]),
itemCount: _filtered.length,
)
Upvotes: 1
Reputation: 1898
You could try filtering the list before you pass it to the builder.
List<String> items;
List<String> _queryResults;
String query;
_queryResults = items.where((item) => item.contains(query)).toList();
Which could then be passed to the ListView.builder without having to set state over and over. Not sure how it would be in performance compared to other options, shouldn't be a big difference as the filtered list will likely be a lot short in most cases. It's a lot cleaner than doing filtering and setting state inside the builder, so I would at least test it out.
Also you should debounce the filtering if you're going for a search-as-you-type solution, which means you wait like 500ms or some fitting duration after each input to see if the user types more before you do the filtering. Will save you a lot of unnecessary calls and make your solution perform better.
Upvotes: 1
Reputation: 2889
You are using for loop inside Listview.builder which is not required. ListView.builder will automatically loop to the number of items in your list...
Try this.
List<String> items;
String query;
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
var item = items[index];
if (item.contains(query)) {
setState(() => _count += 1);
return ItemTile(item);
}
}
Upvotes: 0