Flutter ListView.builder render is cached

Viewed 26

I have a dynamic list view, it looks like this:

final items = [...]; // Many items, which can be changed by some conditional.

ListView.builder(
      itemCount: items.length,
      itemBuilder: (context, index) => SomeWidget(items[index].title)
)

Some conditional (like user text input is changed) will change the items. After the items change, I will call setState to update the listview.

On the debug environment, it works well. But on the profiler/release mode. The listview easy renders cached.

It looks like this: When I type query1 -> it will display all items that match with query1. But after I change to query2 -> SOME TIME, the ListView still keep the render of query1.

BUT, THE MAGIC IS:

  • When I log the build method, I found that the widget is already rebuilt with the new data with query2.
  • BUT, the list view still displays the content of query1.
  • AFTER I touch on the listview, then move my hand to scroll -> The content of query2 is displayed.

I thought that maybe the ListView should rebuild again. So I try to create a microtask with delay of some seconds and then call setState again. But the ListView still displays the old data.


TLDR;

ListView.builder display cache data. After touching to scroll the listview, the new data is displayed.

  • Hard to reproduce on debug mode.
  • Easy to reproduce on profile/release mode.

Flutter version: 3.3.0/3.3.1/3.3.2

Theme: useMaterial3 = true/false (tested with both useMaterial3 and non useMaterial3).

1 Answers

I think you have to add valuekey to your widget, to make sure the widget rebuild when setState is called.

thats common when you separate SomeWidget() in different class, the widget will not rebuild if they have the same key with old widget.

documentation said:

A new widget will only be used to update an existing element if its key is the same as the key of the current widget associated with the element.

ListView.builder(
      itemCount: items.length,
      itemBuilder: (context, index) => SomeWidget(key: UniqueKey(), items[index].title)
)

if the key is unique, every setState you called in your page, the SomeWidget will rebuild, because the current key is different with old key.

but also this will affected to the execution. Because every time setState called, all children in listview.builder will be re-build.

to avoid that, you can set the key to specific children when the textfield updated.

SomeWidget( key: ValueKey(your_key))

Another posibility

there is a lifecycle of widget in listview. It called Destruction

When a child is scrolled out of view, the associated element subtree, states and render objects are destroyed. A new child at the same position in the list will be lazily recreated along with new elements, states and render objects when it is scrolled back.

So, after you scrolled, the children will destroyed, then when you scroll back it will rebuild with the current list value. If your list not updated, the children rendered are based on value before your input from Textfield rendered.

Related