Feeds, chats, settings and search results are all lists, and building them lazily keeps apps fast.
Content taller than the screen must scroll.
- `SingleChildScrollView` makes one child (often a
Column) scrollable. Fine for forms and short pages; it builds everything at once. - `ListView(children: [...])` — a scrolling list of a fixed set of widgets, also built eagerly.
- `ListView.builder(itemCount: n, itemBuilder: (context, i) => ...)` builds items lazily, only those near the viewport. Use it for long or unbounded lists; thousands of items cost no more than a screenful.
- `ListView.separated` adds a separator widget between items.
- `GridView.builder` with a
SliverGridDelegateWithFixedCrossAxisCountor...MaxCrossAxisExtentfor grids.
ListTile is a ready-made row with leading, title, subtitle, trailing and onTap.
Scroll behavior: control position with a ScrollController (create in initState, dispose in dispose), listen for the end of the list to load more, and use RefreshIndicator for pull-to-refresh.
A classic error: putting a ListView directly inside a Column. The list wants infinite height and the Column offers unbounded height, causing "Vertical viewport was given unbounded height". Wrap the list in Expanded so it gets the remaining space. Avoid shrinkWrap: true on long lists — it defeats lazy building.
Give list items stable keys when items can be reordered or removed.
Example
import 'package:flutter/material.dart';
class ContactList extends StatelessWidget {
const ContactList({super.key, required this.names});
final List<String> names;
@override
Widget build(BuildContext context) {
return Column(
children: [
const Padding(padding: EdgeInsets.all(16), child: Text('Contacts')),
Expanded( // give the list bounded height
child: ListView.separated(
itemCount: names.length, // built lazily
separatorBuilder: (context, i) => const Divider(height: 1),
itemBuilder: (context, i) => ListTile(
key: ValueKey(names[i]),
leading: CircleAvatar(child: Text(names[i][0])),
title: Text(names[i]),
onTap: () => debugPrint('tapped ${names[i]}'),
),
),
),
],
);
}
}Remember
ListView.builder/separatedbuild items lazily — use them for long lists- A ListView inside a Column needs
Expanded - ListTile is the standard row; GridView.builder for grids
Watch out for
- Using
shrinkWrap: trueon a long list inside another scroll view, which builds every item.