Flutter Lists: Understanding ListView and ListView.builder
When building Flutter apps, you'll often need to display collections of items—whether it's a list of products, messages in a chat, or search results. Flutter's ListView widget is your go-to solution for creating scrollable lists efficiently. In this article, we'll explore how to use ListView and its powerful ListView.builder constructor to create dynamic, performant lists in your Flutter applications.
What is ListView?
ListView is a scrollable widget that arranges its children linearly. It's one of the most commonly used widgets in Flutter because it handles scrolling automatically and provides excellent performance optimizations. Whether you have a handful of items or thousands, ListView can handle it gracefully.
There are several ways to create a ListView in Flutter:
ListView- For a small, fixed number of childrenListView.builder- For a large or infinite list (lazy loading)ListView.separated- For lists with separators between itemsListView.custom- For advanced customization
Basic ListView
The simplest way to create a list is using the default ListView constructor. You provide a list of widgets as children, and Flutter displays them vertically (or horizontally if you specify a scroll direction).
ListView(
children: [
ListTile(title: Text('Item 1')),
ListTile(title: Text('Item 2')),
ListTile(title: Text('Item 3')),
ListTile(title: Text('Item 4')),
ListTile(title: Text('Item 5')),
],
)
This approach works well when you have a small, known number of items. However, if you have many items or a dynamic list, you'll want to use ListView.builder instead.
ListView.builder: The Power of Lazy Loading
ListView.builder is the recommended approach for most real-world scenarios. It builds items on-demand as they scroll into view, which means it only creates widgets for visible items. This makes it incredibly efficient, even for lists with thousands of items.
Here's how ListView.builder works:
The key parameters for ListView.builder are:
itemCount- The total number of items in the list (optional for infinite lists)itemBuilder- A function that builds each item widget based on its index
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(items[index].title),
subtitle: Text(items[index].subtitle),
);
},
)
A Complete Example
Let's build a practical example that demonstrates ListView.builder in action. We'll create a simple todo list app:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Todo List',
home: TodoListScreen(),
);
}
}
class TodoListScreen extends StatelessWidget {
final List todos = [
'Buy groceries',
'Finish Flutter project',
'Call mom',
'Exercise',
'Read a book',
'Write blog post',
'Plan weekend trip',
'Review code',
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('My Todos'),
),
body: ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
return Card(
margin: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: ListTile(
leading: CircleAvatar(
child: Text('${index + 1}'),
),
title: Text(todos[index]),
trailing: Icon(Icons.chevron_right),
onTap: () {
print('Tapped on: ${todos[index]}');
},
),
);
},
),
);
}
}
In this example, we use ListView.builder to create a scrollable list of todo items. Each item is wrapped in a Card for better visual separation, and we've added interactive elements like a leading avatar, trailing icon, and tap handling.
Understanding the itemBuilder Function
The itemBuilder function is called for each item that needs to be displayed. It receives two parameters:
context- TheBuildContextfor the widget treeindex- The zero-based index of the item being built
The function must return a widget that represents the item at that index. Flutter calls this function lazily—only when an item is about to scroll into view.
ListView.separated: Adding Dividers
When you need visual separation between items, ListView.separated is perfect. It's similar to ListView.builder, but it includes a separatorBuilder function that creates widgets between items.
ListView.separated(
itemCount: items.length,
separatorBuilder: (context, index) => Divider(),
itemBuilder: (context, index) {
return ListTile(
title: Text(items[index]),
);
},
)
This creates a divider between each item. You can customize the separator to be anything—a line, spacing, or even a custom widget.
Horizontal Lists
By default, ListView scrolls vertically. To create a horizontal list, set the scrollDirection property:
ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: items.length,
itemBuilder: (context, index) {
return Container(
width: 200,
margin: EdgeInsets.all(8),
child: Card(
child: Center(child: Text(items[index])),
),
);
},
)
Performance Tips
To get the best performance from your lists, keep these tips in mind:
- Always use ListView.builder for dynamic lists - It's more efficient than creating all widgets upfront
- Provide itemExtent when items have fixed height - This helps Flutter calculate scroll positions more accurately
- Avoid heavy computations in itemBuilder - Keep the builder function lightweight and fast
- Use const constructors when possible - This helps Flutter optimize widget rebuilds
ListView.builder(
itemCount: items.length,
itemExtent: 80.0, // Fixed height for better performance
itemBuilder: (context, index) {
return const ListTile(
title: Text('Item'),
);
},
)
Common Patterns
Here are some common patterns you'll use with ListView.builder:
Loading States
ListView.builder(
itemCount: isLoading ? 1 : items.length,
itemBuilder: (context, index) {
if (isLoading) {
return Center(child: CircularProgressIndicator());
}
return ListTile(title: Text(items[index]));
},
)
Empty States
if (items.isEmpty) {
return Center(
child: Text('No items to display'),
);
}
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(title: Text(items[index]));
},
);
Pull to Refresh
You can wrap your ListView.builder in a RefreshIndicator to add pull-to-refresh functionality:
RefreshIndicator(
onRefresh: () async {
// Fetch new data
await fetchItems();
},
child: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(title: Text(items[index]));
},
),
)
Conclusion
ListView and ListView.builder are essential tools in every Flutter developer's toolkit. They provide an efficient, flexible way to display scrollable collections of items. Remember to use ListView.builder for dynamic lists to take advantage of lazy loading, and keep your item builders lightweight for optimal performance. With these widgets, you can create smooth, responsive lists that handle everything from a few items to thousands of entries.
As you continue building Flutter apps, you'll find that lists are everywhere—from navigation menus to data tables, from chat messages to product catalogs. Mastering ListView will make you a more effective Flutter developer and help you create better user experiences.