Recently, I came across a LinkedIn post where the author mentioned that during interviews with candidates who claimed to be Senior, questions at a Junior or even lower level were not being answered satisfactorily.
This inspired me to create a series of articles to explore and clarify these topics, aiming to consolidate my knowledge and help the community answer these questions more confidently.
In this article, we'll discuss the difference between ListView.builder
and the default ListView
constructor.
What are ListView
and ListView.builder
?
According to the Flutter documentation, ListView
is "a scrollable list of widgets arranged linearly."
On the other hand, ListView.builder
: "creates a scrollable, linear array of widgets that are generated on demand. This constructor is suitable for list views with a large (or infinite) number of children because the builder is only called for those children that are actually visible."
The key difference lies in how the widgets are created and managed.
A simple example of using a ListView is:
ListView(
children: [
ListTile(title: Text('Item 1')),
ListTile(title: Text('Item 2')),
ListTile(title: Text('Item 3')),
],
);
This is a simple way to enable scrolling on a list of items. A similar approach would be:
SingleChildScrollView(
child: Column(
children: [
ListTile(title: Text('Item 1')),
ListTile(title: Text('Item 2')),
ListTile(title: Text('Item 3')),
],
)
);
The behavior would be the same.
For simple lists where the number of items is small and all need to be loaded at once, the default ListView
is a good option.
Now, imagine a scenario where the component displaying the items triggers an event for the Analytics service every time an item is created. If we use ListView.builder
, some items that are never created won't trigger this event, potentially leading to incomplete data in the Analytics service.
But what about ListView.builder
? What does "widgets created on demand" mean? Let's dive deeper into this concept.
On-Demand Rendering
On-demand rendering is a widely used concept, not only in Flutter but in many other technologies, such as:
List
- SwiftUI
RecyclerView
- Android (Java/Kotlin)
LazyColumn
- Jetpack Compose (Android/Kotlin)
UITableView
- UIKit (iOS - Swift)
FlatList
- React Native
VirtualScroller
- Vue.js
react-window
or react-virtualized
- React.js
Nearly every modern technology implements this concept. When dealing with lists containing hundreds or thousands of items, loading everything into memory at once can lead to performance issues, freezing, or even crashes.
The idea is simple: only load into memory what is currently being displayed, along with a few items before and after the visible area, if needed.
For example, imagine a list of 100 items, and the user is viewing items 10 through 15. The technology will load items around that range, such as from positions 5 to 20. This calculation depends on factors like item size, scroll position, and screen size.
A Closer Look at ListView.builder
One important aspect of ListView.builder
is the itemCount
property. Although optional, it helps optimize the rendering process. If you cannot define itemCount
, the itemBuilder
will handle notifying the parent ListView.builder
when there are no more items to render.
The typing of this itemBuilder is: NullableIndexedWidgetBuilder, which in turn is:
NullableIndexedWidgetBuilder = Widget? Function(
BuildContext context,
int index
)
If at any point itemBuilder
returns null, this will stop the creation of further items, even if itemCount
is set.
Example:
ListView.builder(
itemCount: 100,
itemBuilder: (context, index) {
print(index);
return ListTile(
title: Text("Item $index"),
);
},
),
Using an iPhone 15 Pro Max simulator, the console printed me from this list up to index 19, so out of 100 items only 20 of them are in memory. And if we make the following edit:
ListView.builder(
itemCount: 100,
itemBuilder: (context, index) {
if (index == 50) {
return null;
}
print(index);
return ListTile(
title: Text("Item $index"),
);
},
),
The display will display up to element 50, index 49.
Stay tuned!
Therefore, understanding how these properties work together is crucial to avoid inconsistencies in list rendering.
Conclusion
Using ListView.builder
is generally recommended to reduce memory usage, especially on older devices. However, we’ve seen that there are scenarios where the traditional ListView
may be a better fit.
There is still much more to explore, such as ListView.custom
, ListView.separated
, and integrations with SliverChildBuilder
. But with the concepts discussed here, you should now be able to understand the main differences between these two widgets and when to use them.
Top comments (0)