Master Flutter Lists: ListView, GridView & Builder | (Ch. 10)

Flutter Course Chapter 10: Mastering Lists and Grids, showing mobile screens with vertical scrolling lists and tile-based grid layouts.


Welcome to Chapter 10! We have reached double digits.

Think about the apps you use every day. Instagram? It's a scrollable list of photos. WhatsApp? A scrollable list of chats. YouTube? A scrollable list of videos. Your phone's Settings? A scrollable list of options.

90% of mobile app development is just displaying lists of data.

So far, we've used Column to stack widgets. But Column has a fatal flaw: it doesn't scroll. If you put too many items in a Column, you get the dreaded yellow-and-black "Overflow" error.

In this chapter, we are going to solve that. We will master the ListView and GridView widgets. We will learn how to make static lists, but more importantly, we will learn how to use Builders to efficiently display thousands of items without crashing your app.

1. The Basic `ListView` (Static Lists)

The simplest way to make a list is to replace your Column with a ListView.

A basic ListView takes a list of children, just like a Column. The difference is that a ListView automatically provides scrolling when the items overflow the screen.


@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text('Basic List')),
    body: ListView(
      padding: EdgeInsets.all(20),
      children: [
        Text('Item 1', style: TextStyle(fontSize: 24)),
        Text('Item 2', style: TextStyle(fontSize: 24)),
        Text('Item 3', style: TextStyle(fontSize: 24)),
        // Imagine 50 more items here...
        Container(height: 200, color: Colors.red),
        Container(height: 200, color: Colors.blue),
        Container(height: 200, color: Colors.green),
      ],
    ),
  );
}

Run this, and try to drag the screen up and down. It scrolls! It also gives you a nice "bounce" effect on iOS or a "glow" effect on Android automatically.

Vertical vs. Horizontal Scrolling

By default, lists scroll vertically. But think about the "Stories" bar on Instagram or Netflix movie categories. They scroll horizontally.

You can change the scroll direction easily:


ListView(
  scrollDirection: Axis.horizontal, // Changes flow to Left -> Right
  children: [
    Container(width: 100, color: Colors.red),
    Container(width: 100, color: Colors.green),
    Container(width: 100, color: Colors.blue),
  ],
)
Note: If you put a horizontal `ListView` inside a vertical `Column`, you must wrap the `ListView` in a `SizedBox` or `Expanded` to give it a defined height. Otherwise, Flutter won't know how tall to make the list and will crash.

2. The Best Friend: `ListTile`

In Chapter 5 (the Business Card project), we manually built rows using Row, Icon, and Text.

Flutter gives us a specialized widget designed specifically for lists: the ListTile. It conforms to Material Design standards and creates perfectly aligned rows.

A ListTile has three main slots:

  • leading: An widget on the far left (usually an Icon or Avatar).
  • title: The main text.
  • subtitle: Smaller text below the title.
  • trailing: A widget on the far right (usually an arrow or checkbox).

ListView(
  children: [
    ListTile(
      leading: Icon(Icons.map),
      title: Text('Map'),
      subtitle: Text('Navigate the world'),
      trailing: Icon(Icons.chevron_right),
      onTap: () {
        print('Map Tapped');
      },
    ),
    ListTile(
      leading: Icon(Icons.photo_album),
      title: Text('Album'),
      subtitle: Text('View your photos'),
      trailing: Icon(Icons.chevron_right),
    ),
    ListTile(
      leading: Icon(Icons.phone),
      title: Text('Phone'),
      trailing: Switch(value: true, onChanged: (v){}),
    ),
  ],
)

Notice the onTap property? `ListTile` comes with built-in touch detection and a nice "splash" effect when tapped. You don't even need a `GestureDetector`!

3. The Professional Way: `ListView.builder`

Using ListView(children: []) is fine for settings screens with 10-20 items.

But what if you are building a social media feed with 10,000 posts?

Why `Column` and `ListView()` are bad for big data

If you create a standard `ListView` with 10,000 items, Flutter tries to build all 10,000 widgets immediately when the app loads.

This will freeze your app, consume all your memory (RAM), and likely crash the phone.

Lazy Loading Explained

The solution is "Lazy Loading." We only want to build the widgets that are currently visible on the screen.

If your phone screen fits 8 items, we should only build 8 items (plus maybe 1 or 2 extra as a buffer). As you scroll down, Flutter destroys the ones that go off the top and builds the new ones coming in from the bottom.

This keeps memory usage low and performance high, whether you have 50 items or 5 million items.

Using `itemBuilder`

To do this, we use the constructor ListView.builder. It takes two main arguments:

  1. `itemCount`: How many items are in the list total?
  2. `itemBuilder`: A function that runs for every visible item. It gives you the index (0, 1, 2...) and asks you to return a Widget for that specific position.

// Let's imagine we have a list of data
final List<String> myPets = ['Dog', 'Cat', 'Fish', 'Bird', 'Hamster'];

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: ListView.builder(
      // 1. Tell Flutter how many items there are
      itemCount: myPets.length,
      
      // 2. Build the widget for index 'i'
      itemBuilder: (BuildContext context, int index) {
        return ListTile(
          leading: CircleAvatar(
            child: Text((index + 1).toString()), // Show 1, 2, 3...
          ),
          title: Text(myPets[index]), // Get data from our list
        );
      },
    ),
  );
}

This code is incredibly efficient. itemBuilder is a loop that only runs when it needs to.

4. Adding Separators: `ListView.separated`

Often in UI design, you want a thin grey line between list items to separate them.

Instead of manually adding a `Divider()` widget inside your builder logic, use ListView.separated. It works exactly like `.builder`, but has one extra argument: separatorBuilder.


ListView.separated(
  itemCount: 50,
  itemBuilder: (context, index) {
    return ListTile(title: Text('Item $index'));
  },
  // This runs BETWEEN every item
  separatorBuilder: (context, index) {
    return Divider(color: Colors.grey);
  },
)

5. Grid Layouts: `GridView`

Sometimes you don't want a list; you want a grid (like an Instagram profile or a Photo Gallery). For this, we use GridView.

Like `ListView`, `GridView` has scroll capability built-in.

`GridView.count` (Fixed Columns)

Use this when you know exactly how many columns you want (e.g., 2 columns).


GridView.count(
  crossAxisCount: 2, // 2 Columns wide
  crossAxisSpacing: 10, // Space between columns
  mainAxisSpacing: 10, // Space between rows
  padding: EdgeInsets.all(10),
  children: [
    Container(color: Colors.red),
    Container(color: Colors.blue),
    Container(color: Colors.green),
    Container(color: Colors.yellow),
  ],
)

`GridView.builder` (Dynamic Grid)

Just like `ListView.builder`, if you have hundreds of photos, use `GridView.builder`. It requires a special `gridDelegate` object.


GridView.builder(
  itemCount: 100,
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 3, // 3 items per row
  ),
  itemBuilder: (context, index) {
    return Container(
      margin: EdgeInsets.all(2),
      color: Colors.blue[100 * (index % 9)], // Different shades of blue
      child: Center(child: Text('$index')),
    );
  },
)

6. Mini-Project: Building a "Contacts List"

Let's combine everything. We will create a contact list using a `List` of Maps (data), `ListView.builder` (efficiency), and `ListTile` (design).

Create a new file or clear your `main.dart` and use this:


import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(home: ContactListScreen()));
}

class ContactListScreen extends StatelessWidget {
  // 1. Our Dummy Data
  final List<Map<String, String>> contacts = [
    {'name': 'Alice Johnson', 'email': 'alice@example.com'},
    {'name': 'Bob Smith', 'email': 'bob@example.com'},
    {'name': 'Charlie Brown', 'email': 'charlie@gmail.com'},
    {'name': 'David Wilson', 'email': 'david@yahoo.com'},
    {'name': 'Eva Green', 'email': 'eva@example.com'},
    {'name': 'Frank White', 'email': 'frank@test.com'},
    {'name': 'Grace Lee', 'email': 'grace@demo.com'},
    {'name': 'Henry Ford', 'email': 'henry@cars.com'},
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('My Contacts')),
      body: ListView.builder(
        itemCount: contacts.length,
        itemBuilder: (context, index) {
          // 2. Get the specific contact for this row
          final contact = contacts[index];
          
          // 3. Return the UI
          return ListTile(
            // Create a circle with the first letter of their name
            leading: CircleAvatar(
              backgroundColor: Colors.blue,
              child: Text(
                contact['name']![0], // First letter
                style: TextStyle(color: Colors.white),
              ),
            ),
            title: Text(contact['name']!),
            subtitle: Text(contact['email']!),
            trailing: Icon(Icons.phone, color: Colors.green),
            onTap: () {
              print("Calling ${contact['name']}");
            },
          );
        },
      ),
    );
  }
}

What just happened?

  1. We defined a List of data. In a real app, this would come from a database or API.
  2. We used ListView.builder so it's scalable.
  3. Inside the builder, we accessed contacts[index] to get the specific data for *that* row.
  4. We used ListTile to automatically format the name, email, and avatar.
  5. We added an onTap to handle interaction.

Conclusion

You have mastered the scroll. You can now handle 10 items or 10,000 items without breaking a sweat.

You've learned:

  • How ListView solves the scrolling problem of Column.
  • How ListTile creates beautiful rows instantly.
  • The critical importance of Lazy Loading with ListView.builder.
  • How to create grids with GridView.

In the next chapter, we will put your skills to the test. We are going to build Project 2: A "Simple To-Do List" UI. This will combine everything from layout to lists to buttons into a single, functional interface.

See you there!

Comments