Skip to content

Intermediate · Lists, navigation and forms · topic 22 of 50 · 30 min

Lists with ListView

Explanation

ListView shows a scrollable list. Use the plain ListView(children: [...]) only for short, fixed lists. For long or dynamic lists use ListView.builder, which builds items lazily as they scroll into view. ListView.separated adds dividers. ListTile gives a ready row layout, Dismissible adds swipe-to-delete, and RefreshIndicator adds pull-to-refresh.

Why ListView.builder?

Most apps are lists: chats, products, transactions, notifications. A list may have thousands of items. Building every item at once wastes memory and time, especially on budget phones. ListView.builder builds only the items currently visible (plus a few around them) and builds new ones as the user scrolls. It is called lazy building.

dart
class Txn {
  const Txn(this.title, this.amount);
  final String title;
  final int amount;
}

class StatementList extends StatelessWidget {
  const StatementList({super.key, required this.txns});
  final List<Txn> txns;

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: txns.length,
      itemBuilder: (context, index) {
        final t = txns[index];
        return ListTile(
          leading: Icon(t.amount < 0 ? Icons.arrow_upward : Icons.arrow_downward),
          title: Text(t.title),
          trailing: Text('Rs ${t.amount.abs()}'),
        );
      },
    );
  }
}
itemBuilder is called with an index for each item that needs to appear.

itemCount tells the builder how many items exist, so the list knows where it ends and how long the scrollbar should be. Leave it out only for truly endless lists, and then return null from itemBuilder when there are no more items. Each call to itemBuilder should be cheap: read the item, build a small widget, nothing more.

Lists usually get their data from state (a List in a StatefulWidget, a Provider or a Bloc). When that list changes and the widget rebuilds, ListView.builder shows the new items; you never add or remove rows by hand.

The three ListView constructors

ConstructorBuilds itemsUse for
ListView(children: [...])All at onceShort, fixed lists, such as a settings page
ListView.builderLazily, on demandLong or dynamic lists
ListView.separatedLazily, with a separator between itemsLists with dividers
dart
ListView.separated(
  itemCount: contacts.length,
  separatorBuilder: (context, index) => const Divider(height: 1),
  itemBuilder: (context, index) => ListTile(title: Text(contacts[index])),
)
The separator is built between items, not after the last.

ListTile

ListTile is a Material row layout with slots: leading (an icon or avatar), title, subtitle, trailing (an icon, a switch or text) and onTap. It handles padding, height and accessibility for you, so most list rows can start as a ListTile.

Swipe and pull

dart
RefreshIndicator(
  onRefresh: () async => await reloadOrders(),       // must return a Future
  child: ListView.builder(
    itemCount: orders.length,
    itemBuilder: (context, i) => Dismissible(
      key: ValueKey(orders[i].id),                    // a unique, stable key per item
      onDismissed: (_) => removeOrder(orders[i].id),
      background: Container(color: Colors.red),
      child: ListTile(title: Text(orders[i].title)),
    ),
  ),
)
Pull down to refresh; swipe an item away to delete it. Dismissible needs a unique key.

A key is a stable identity for a widget. When items move or are removed, keys let Flutter match old and new items correctly, so a swipe or animation affects the right row. Use something unique from your data, such as an id, with ValueKey.

Think of it like this: ListView.builder is like a restaurant that cooks each dish only when a customer orders it, instead of cooking the whole menu every morning. Customers (the screen) only ever see a few dishes at a time, so cooking on demand saves a lot of effort.

Common mistakes

  • Using ListView(children: list.map(...).toList()) for hundreds of items: slow. Use ListView.builder.
  • Putting a ListView inside a Column without Expanded: unbounded height error (module 5).
  • Using the index as the Dismissible key: after a delete, indexes shift and the wrong item is affected. Use a unique id.
  • Doing heavy work (date formatting, parsing) inside itemBuilder for every item: prepare data beforehand.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Lists with ListView · 1/6

ListView

Most apps are lists. ListView shows a scrollable list of widgets.

Prefer a video by a teacher? Find free videos on “flutter listview builder separated listtile dismissible refreshindicator” on YouTube ↗

In real life: A notification list

A banking app shows hundreds of notifications. ListView.separated builds only the visible ones, each a ListTile with an icon, title and time. Users pull down to refresh and swipe to delete a notification. Because each Dismissible uses the notification's id as its key, deleting one never removes the wrong row.

dart
ListView.separated(
  itemCount: notes.length,
  separatorBuilder: (_, __) => const Divider(height: 1),
  itemBuilder: (context, i) {
    final n = notes[i];
    return ListTile(
      leading: const Icon(Icons.notifications),
      title: Text(n.title),
      subtitle: Text(n.time),
      onTap: () => openNote(n.id),
    );
  },
)
notes, openNote and the note fields are your own data and functions.

Practice (0/3 done)

Task 1 · Question

Which is best for a chat list with 5,000 messages?

Task 2 · Question

Why should a Dismissible use the item's id rather than its index as the key?

Task 3 · Practice

Build a shopping list screen: a ListView.separated of items (name and quantity) using ListTile, swipe-to-delete with Dismissible and a SnackBar "Removed X", pull-to-refresh that resets the list after a short delay, and an empty-state message when the list is empty.

Quick quiz

Question 1

1.What does lazy building mean for ListView.builder?

Question 2

2.Which ListTile slot usually holds an icon or avatar at the start?

Question 3

3.What must RefreshIndicator's onRefresh return?

Question 4

4.When is the plain ListView(children: [...]) a good choice?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What does lazy building mean for ListView.builder?

Answer: Items are built only when they are about to be visible

Saves memory and time.

Which ListTile slot usually holds an icon or avatar at the start?

Answer: leading

leading is at the start.

What must RefreshIndicator's onRefresh return?

Answer: A Future

The spinner shows until the Future completes.

When is the plain ListView(children: [...]) a good choice?

Answer: For short, fixed lists

All children are built at once.

What is the problem with shrinkWrap: true on a long ListView?Related

Answer: It builds all items at once, hurting performance

Prefer Expanded or a single scrollable.

From Understanding constraints and fixing overflow

Which is the right way to build a scrolling screen with a header and a long list?Related

Answer: One LazyColumn with item { header } and items { }

Nested same-direction scrolling crashes.

From Lists and grids

Which widget adds a tap ripple to a tile?Related

Answer: InkWell

InkWell shows Material ink feedback.

From Grids with GridView

Which object holds a StatefulWidget's State between rebuilds?Related

Answer: The element

Elements are long-lived.

From Interview questions: Flutter

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 4 more topics in “Lists, navigation and forms” before its exam: Grids with GridView, Navigator basics, Routing with go_router, Forms and validation.
  2. Module 6 exam: Lists, navigation and forms · 13 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, State management. 50 topics left in the course before the final exam.

← Previous: Images, icons, fonts and themes