I wanted to convert a ListView into a GridView so that it would show more information on a large screen such as Web.

My ListView code looked like this:

return ListView.builder(
  physics: scrollable ? null : NeverScrollableScrollPhysics(),
  shrinkWrap: shrinkWrap,
  padding: EdgeInsets.only(top: 16.0),
  itemCount: words.length,
  itemBuilder: (context, index) =>
      buildListItem(context, words[index]),
);

I found that GridView used almost the same parameters, and took on top of that a gridDelegate.

The doc says that for large or infinite grids, you should use the builder constructor. Good, that's what I'm looking for. Also for gridDelegates, they list two of them:

Looking at the description for MaxCrossAxisExtent, which says it'll divide columns if the width of the item is larger than a threshold, this is perfect for me! And here I thought I'd need a LayoutBuilder and do width/column/spacing maths.

So I tried introduced the GridView.builder with the delegate, and ran it. It automatically created the columns as expected. However, the height of the grid items was all wrong. I thought it had to do with how my items had some Columns in them. I tried to set the Columns' mainAxisSize to min. It did nothing.

After googling it, I realized that it was because by default, the childAspectRatio is 1, so it will make square tiles. See two results:

Judging by these posts, it seems like Flutter doesn't provide a way to set a height besides computing an aspect ratio yourself. Some users provide some fancy maths, while one other person suggested to copy/paste reimplement their own version of a delegate like SliverGridDelegateWithFixedCrossAxisCountAndFixedHeight.

Out of curiosity, instead of reimplementing the whole class, I wanted to see if it was possible to extend the desired class, and override the getLayout method to use the same code, except for the height value.

As I browsed through the code, I found this (source code link):

final double childMainAxisExtent = mainAxisExtent ?? childCrossAxisExtent / childAspectRatio;

Wait, so there's a way to not use childAspectRatio? What is this mainAxisExtent? It turns out, it's an optional parameter, and it does exactly what we wanted (source code link):

/// The extent of each tile in the main axis. If provided it would define the
/// logical pixels taken by each tile in the main-axis.
///
/// If null, [childAspectRatio] is used instead.
final double? mainAxisExtent;

With hindsight, the parameter was in the documentation all along. https://api.flutter.dev/flutter/rendering/SliverGridDelegateWithMaxCrossAxisExtent/mainAxisExtent.html

TLDR

Here's the final piece of code:

return GridView.builder(
  gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
    maxCrossAxisExtent: 500,
    mainAxisExtent: 77,
  ),
  physics: scrollable ? null : NeverScrollableScrollPhysics(),
  shrinkWrap: shrinkWrap,
  padding: EdgeInsets.only(top: 16.0),
  itemCount: words.length,
  itemBuilder: (context, index) =>
      buildListItem(context, words[index]),
);