In a Flutter ListView.builder, what do itemExtent and prototypeItem save, and when would you use itemExtentBuilder instead?
answer
- no measuring each row
- index from offset arithmetic
- prototype measured once
- only one of the three
- varied but known heights
basics
~20 sBoth tell the list each item's main-axis extent up front, so it finds the index at any offset without laying out the items in between. itemExtent gives a number, prototypeItem measures one sample widget, and itemExtentBuilder gives a known extent per index.
solid answer
~50 sWithout an extent, a `ListView.builder` learns each row's height only by laying it out, so a big jump — a `jumpTo` near the end, or dragging the scrollbar — makes it lay out every row in between, and the total extent is estimated from the average of the rows seen so far. `itemExtent: 72` switches the list to a fixed-extent sliver: the first visible index is just offset divided by 72, and with `itemCount` the total length is exact. Children are forced to that extent, so taller content is squeezed or overflows. `prototypeItem` does the same using the measured size of one sample widget, useful when the height depends on fonts or text scale. `itemExtentBuilder(index, dimensions)` returns a known extent per index, for rows that differ but are predictable, such as section headers. Only one of the three may be set.
code
dart · 31 linesimport 'package:flutter/material.dart';
class PartRow extends StatelessWidget {
const PartRow({super.key, required this.name, required this.number});
final String name;
final String number;
@override
Widget build(BuildContext context) {
return ListTile(title: Text(name, maxLines: 1), subtitle: Text(number));
}
}
class PartsList extends StatelessWidget {
const PartsList({super.key, required this.names, required this.numbers});
final List<String> names;
final List<String> numbers;
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: names.length,
prototypeItem: const PartRow(name: 'Brake pad set, front axle', number: 'BP-000000'),
itemBuilder: (BuildContext context, int index) {
return PartRow(name: names[index], number: numbers[index]);
},
);
}
}go deeper
Know that setting itemExtent or prototypeItem on a list of same-height rows makes scrolling cheaper.
Explain the cost of measuring rows on big jumps, the tight constraint itemExtent imposes, and why only one of the three may be set.
Choose between the three from the row design and text scaling, and diagnose clipped rows or jumpy scrollbars from the wrong choice.
Encourage row designs with predictable heights in large data lists, trading some visual freedom for smooth scrolling and exact scrollbars.
## The cost they remove A lazy list must answer two questions on every scroll: *which index is at this offset?* and *how long is the whole list?* When items size themselves, the only way to know an item's extent is to lay it out. - **Jumping far** — `jumpTo` near the end, dragging a scrollbar thumb, restoring an offset — requires laying out every item between the last known one and the target. - **Total length** is extrapolated from the average extent of the items laid out so far, so the scrollbar thumb can change size as the user scrolls. In a 5,000-part catalogue, dragging the scrollbar to the bottom can therefore build and lay out thousands of rows in one frame. ## The three options | Parameter | Internal sliver | Extent comes from | |---|---|---| | `itemExtent` | `SliverFixedExtentList` | one fixed `double` | | `prototypeItem` | `SliverPrototypeExtentList` | the measured size of one sample widget | | `itemExtentBuilder` | `SliverVariedExtentList` | a callback per index | They are **mutually exclusive**: the constructor asserts that you pass at most one. ### itemExtent With a fixed extent, finding the first visible index is arithmetic, offset divided by extent, and with `itemCount` the maximum scroll extent is exact. Each child is laid out with a **tight** main-axis constraint of that extent: a row whose content needs more height is squeezed or overflows, and one that needs less is stretched. ```dart ListView.builder( itemCount: parts.length, itemExtent: 72, itemBuilder: (BuildContext context, int index) => PartRow(part: parts[index]), ) ``` ### prototypeItem A hard-coded 72 breaks when the user increases text size or the theme changes font metrics. `prototypeItem` takes a representative widget, lays it out once, and uses its main-axis size for every row. Choose a prototype with the *longest* realistic content — a long part name, a two-line subtitle — so real rows are not cut off. ### itemExtentBuilder `itemExtentBuilder` has the signature `double? Function(int index, SliverLayoutDimensions dimensions)`. It suits lists whose rows differ in predictable ways, such as a 40-pixel category header followed by 72-pixel part rows. It is called several times per layout, so keep it cheap, and return `null` for indexes beyond the end. ## When not to use them - Rows whose height depends on unpredictable content, such as free-text notes that wrap differently. Forcing an extent clips them; let them size themselves. - Lists short enough that jump cost never matters. ## Checklist 1. Are all rows the same height? Use `itemExtent`, or `prototypeItem` if that height depends on text metrics. 2. Do heights vary by a rule you can compute from the index? Use `itemExtentBuilder`. 3. Otherwise, leave all three unset and accept estimated extents. 4. Always pass `itemCount` when the length is known, so the fixed-extent math can give an exact total. ## Summary These parameters trade layout flexibility for arithmetic: the list stops measuring rows to find its place. That matters most for long lists with scrollbars, jumps and restored positions.
- Why is prototypeItem often safer than a hard-coded itemExtent?`prototypeItem` measures a real widget, so its height follows the theme's fonts and the user's text scale. A hard-coded `itemExtent` stays fixed while text grows, so rows get clipped at larger text sizes.
- What happens to a row whose content is taller than the itemExtent?Each child is laid out with a tight main-axis constraint equal to `itemExtent`, so it cannot grow. Its content is squeezed and may report an overflow, and the next row starts exactly one extent later.
saying these in an interview costs you the question
- itemExtent is only a hint that rows may exceed.
- You can combine itemExtent with prototypeItem for extra speed.
- prototypeItem is rendered as the first row of the list.
- Without an extent, the list always knows its exact total length.
- itemExtentBuilder is called once per item and cached forever.