In my Flutter app, I use an AnimatedList
, with 100 items, and only a few can be displayed at once on the screen.
When I first run it, I can see in the logs that only the necessary items are built. But if I refresh the content of my list, for some reason, the AnimatedList
rebuilds every item, which makes my app laggy.
Here is the code :
class _MyHomePageState extends State<MyHomePage> {
static const _durationMin = Duration(microseconds: 1);
final List<String> _items = List.generate(100, (index) => "Item at $index");
final GlobalKey<AnimatedListState> _listKey = GlobalKey();
late ScrollController _scrollController;
@override
void initState() {
super.initState();
_scrollController = ScrollController(initialScrollOffset: 0);
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
void _onUpdate() {
_listKey.currentState?.removeAllItems((_, __) => const SizedBox.shrink(), duration: _durationMin);
_listKey.currentState?.insertAllItems(0, _items.length, duration: _durationMin);
}
Widget _buildItemWidget(String text) {
return ListTile(
title: Text(text),
trailing: IconButton(
icon: const Icon(Icons.remove),
onPressed: () {},
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: SafeArea(
child: AnimatedList(
key: _listKey,
padding: EdgeInsets.zero,
controller: _scrollController,
initialItemCount: _items.length,
itemBuilder: (context, index, animation)
{
print("index : $index");
return _buildItemWidget(_items[index]);
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: _onUpdate,
child: const Icon(Icons.refresh),
),
);
}
}
So when I run the app, here is what I see in the logs:
index : 0
...
index : 18
But if I press the floating action button, here is what I see in the logs:
index : 0
...
index : 99
Why do I have that behavior? How can I fix that so when I press the floating action button, it only rebuilds the necessary items?
Thanks for your help
If you see the documentation of removeAllItems
, it says:
Items are removed immediately. However, the items will still appear for duration, and during that time builder must construct its widget as needed.
Which means the SizedBox.shrink()
widget will remains for _durationMin
, but you immediately called insertAllItems
after so that the builder will build all 100 items with the SizedBox.shrink()
widget. SizedBox.shrink()
has a height of 0, which means all of them are visible on the screen so they are all built.
There are some scenarios you can try:
removeAllItems
to Duration.zero
, you can see it will work as expected (items are built only until index 18). This is because at the time insertAllItems
is called, the widgets built are those ListTile
which has actual height.SizedBox.shrink()
with Text('test')
, you will see the number of widgets built are decreased (not until index 99, but only until index 51, in my case), because now there are less widgets visible on the screen.Note: When I say "visible on the screen", it also includes buffered items that comes after the last visible item.