← Back to Articles

Flutter Lists: Understanding ListView and ListView.builder

Flutter Lists: Understanding ListView and ListView.builder

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 children
  • ListView.builder - For a large or infinite list (lazy loading)
  • ListView.separated - For lists with separators between items
  • ListView.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:

ListView.builder itemCount itemBuilder Widgets Built On-Demand

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 - The BuildContext for the widget tree
  • index - 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.builder Lazy Loading Only visible items are built Item 0 Built Item 1 Built Item 2 Built Item 3 Not Built Item 4 Not Built As user scrolls, more items are built on-demand

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.