GridView.builder in Flutter: Efficient Lazy Grids at Any Size
Build efficient lazy grids in Flutter — choosing delegates, computing childAspectRatio, responsive column counts, sliver integration, and performance pitfalls.
Published on • November 2, 2026
AI Assistant

Grids are the default layout for galleries, catalogs, and dashboards, and GridView.builder is the variant you should reach for because it only constructs the cells on screen (docs.flutter.dev). Picking the right delegate and aspect ratio is most of the work.
GridView.builder builds only what is visible
itemCount + itemBuilder let Flutter create and dispose cells lazily as the user scrolls — never build your own List<Widget> for a grid of thousands of items.
GridView.builder(
padding: const EdgeInsets.all(12),
itemCount: products.length,
itemBuilder: (ctx, i) =>
Card(child: Image.network(products[i].url, fit: BoxFit.cover)),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, crossAxisSpacing: 12, mainAxisSpacing: 12,
childAspectRatio: 3 / 4,
),
);
FixedCrossAxisCount vs MaxCrossAxisExtent
childAspectRatio is width / height: a cell 160px wide that must be 200px tall needs 0.8, not 1.25. Use FixedCrossAxisCount when the column count is a design decision, and MaxCrossAxisExtent when the width should decide it.
// Always exactly 3 columns:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, crossAxisSpacing: 8, mainAxisSpacing: 8,
childAspectRatio: 1,
)
// Cells never wider than 180px — columns adapt to width:
const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 180, crossAxisSpacing: 8, mainAxisSpacing: 8,
childAspectRatio: 1,
)
Responsive crossAxisCount from MediaQuery
Compute the count once in build and feed it to the delegate — or skip the math entirely with MaxCrossAxisExtent above.
final w = MediaQuery.sizeOf(context).width;
final crossAxisCount = w > 1024 ? 5 : w > 768 ? 4 : w > 600 ? 3 : 2;
SliverGrid inside CustomScrollView
When the grid shares a scroll view with headers and other lists, extract it as a SliverGrid instead of nesting scrollables.
CustomScrollView(
slivers: [
const SliverToBoxAdapter(child: SectionHeader('Catalog')),
SliverGrid(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, childAspectRatio: 0.75),
delegate: SliverChildBuilderDelegate(
(ctx, i) => ProductTile(product: products[i]), childCount: products.length),
),
],
)
Gotchas and best practices
- Avoid
shrinkWrap: trueon grids inside scrollables — it lays out every child; useSliverGridinstead. GridView.count/GridView.extentare prototype shorthands;builderis the production default.- Raise
cacheExtentpast the default 250px for expensive cells (images, blur), and give stateful cells akey(ValueKey(item.id)) so their state follows the item through reorders.
A lazy GridView.builder with the right delegate and aspect ratio scales from a dozen thumbnails to a catalog without changing a line of layout code.