flutter-listas-listview-builder

Efficient Lists in Flutter with ListView.builder

  • 5 min

A ListView is a widget specialized in displaying scrollable lists of items.

In the previous post we saw that SingleChildScrollView is useful, but dangerous for performance if we have a lot of content. Today we’re going to meet its older brother, the collections specialist: the ListView.

Lists are the most common component in mobile applications. Think of WhatsApp (chat list), Instagram (photo feed) or Spotify (song list). They all use the same principle.

But in Flutter, not all ListView are the same. Choosing the wrong constructor can make your App laggy. Let’s see why.

ListView with explicit children

The most basic way to use this widget is with its default constructor. It’s very similar to using a Column inside a scroll, but with less code.

ListView(
  children: [
    ListTile(title: Text("Option 1"), leading: Icon(Icons.map)),
    ListTile(title: Text("Option 2"), leading: Icon(Icons.photo)),
    ListTile(title: Text("Option 3"), leading: Icon(Icons.phone)),
    // ... imagine 50 more options
  ],
)
Copied!

I’ve used the ListTile widget. It’s a prefabricated Flutter widget that follows Material Design guidelines for lists: it has a title, subtitle, a leading icon (leading) and a trailing icon (trailing).

The problem with the static ListView

Although it’s better than SingleChildScrollView, the normal ListView has the same “defect”: it tries to render its children all at once.

It’s perfect for a settings menu with 10 or 20 fixed options. But if you try to load a list of 5,000 products from a database, your RAM will suffer.

Lazy Construction

Imagine you have a list of 1,000 items, but your mobile screen only fits 10. Does it make sense to waste battery and memory painting the 990 that aren’t visible? No.

Flutter solves this by drawing only what is in the “Viewport” (what’s visible on screen) plus a small safety margin. As you scroll, Flutter destroys the elements that go out at the top and creates the ones that come in at the bottom.

To use this technique, we need the ListView.builder constructor.

ListView.builder

This constructor does not receive a list of children (children). Instead, we give it “instructions” on how to manufacture each element on demand.

It needs two key parameters:

  1. itemCount: How many items there are in total.
  2. itemBuilder: A function that runs every time an item is about to enter the screen.

How itemBuilder works

This function receives the context and an index (an integer indicating which position in the list is being drawn: 0, 1, 2…).

final nameList = ["Ana", "Luis", "Carlos", "Marta", "Pedro", ...];

ListView.builder(
  itemCount: nameList.length, // We tell it the total (e.g., 100)
  itemBuilder: (context, index) {
    
    // This function runs in a loop, but ONLY for the visible items.
    // We use the 'index' to access the data.
    final name = nameList[index];

    return ListTile(
      leading: CircleAvatar(child: Text(name[0])), // First letter
      title: Text(name),
      subtitle: Text("Position in list: $index"),
    );
  },
)
Copied!

If you run this code with a list of 10,000 names, the App will be perfectly smooth. Flutter will only be managing the ~10 widgets that fit on your screen at that moment.

ListView.separated for adding separators

Often in designs, we are asked to put a gray line (Divider) between each item to visually separate them.

We could try to put the Divider inside the itemBuilder, but that would also put a line after the last element (or we would have to use ugly logic with if).

Flutter gives us the ListView.separated constructor.

ListView.separated(
  itemCount: 50,
  // Normal item builder
  itemBuilder: (context, index) {
    return ListTile(title: Text("Item $index"));
  },
  // Separator builder (automatically interleaved)
  separatorBuilder: (context, index) {
    return Divider(color: Colors.blueAccent);
  },
)
Copied!

This will draw: Item 0 -> Separator -> Item 1 -> Separator -> Item 2…

Common mistakes

Not setting size constraints

Just like with Column, a ListView tries to take up all the available height. If you put a ListView inside another ListView or inside a Column without using Expanded, you will get infinite size errors.

Forgetting itemCount

If you don’t specify itemCount in ListView.builder, the list will be infinite. This isn’t necessarily an error (sometimes we want infinite scrolling), but if you access myList[index] and the index exceeds the size of your array, the App will crash (RangeError).

Not using keys when reordering

If you are going to allow the user to delete or move items in the list, each item needs a unique Key so that Flutter doesn’t get confused when recycling the widgets. (We’ll see this in depth in advanced modules, but keep it in mind).

Which constructor to use

ConstructorWhen to use it?Example
ListViewSmall, pre-known lists.Options menu, Settings.
ListView.builderLarge or unknown lists, or those coming from an API.News feed, Contact list.
ListView.separatedSame as builder, but you need separators.Email list with dividing lines.