You are encountering an issue with the SingleChildScrollView
that contains ListView
s.
A ListView
will attempt to take as much space as possible for its axis. In your case, the vertical axis. However, the vertical axis is unbounded, because a SingleChildScrollView
has an infinite height.
The solution is simply to tell your ListView
s to only take up as much space as possible:
ListView renderCardsList(List<MyObject> list) {
return ListView.builder(
/// When 'shrinkWrap' is set to true, the list will only be as big as its
/// children, instead of attempting to fill the available space.
shrinkWrap: true, // <=== RIGHT HERE!
itemCount: list.length,
itemBuilder: (context, index) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 5.0),
child: MyCard(object: list[index]),
),
);
}
Without shrinkWrap
being set to true
, the framework will not be able to lay out your list views in an unbounded vertical axis.
I created mock data and put multiple ListView
s inside a Column
, inside a SingleChildScrollView
:
return SingleChildScrollView(
primary: false,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (elementsFromThePast.isNotEmpty) ...[
const Text("Past"),
renderCardsList(elementsFromThePast),
],
if (elementsForToday.isNotEmpty) ...[
const Text("Today"),
renderCardsList(elementsForToday),
],
if (elementsFromTheFuture.isNotEmpty) ...[
const Text("Future"),
renderCardsList(elementsFromTheFuture),
],
],
),
);
Output using shrinkWrap
:

Here is the full example code:
extension DateTimeExtension on DateTime {
DateTime removeTimeComponents() {
return DateTime(year, month, day, 0, 0, 0);
}
}
class MyGroupedListViews extends StatelessWidget {
const MyGroupedListViews({super.key});
@override
Widget build(BuildContext context) {
final list = getMockList();
final today = DateTime.now().removeTimeComponents();
final elementsFromThePast = list
.where((element) => element.date.isBefore(today))
.toList(growable: false);
final elementsForToday =
list.where((element) => element.date == today).toList(growable: false);
final elementsFromTheFuture = list
.where((element) => element.date.isAfter(today))
.toList(growable: false);
return SingleChildScrollView(
primary: false,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (elementsFromThePast.isNotEmpty) ...[
const Text("Past"),
renderCardsList(elementsFromThePast),
],
if (elementsForToday.isNotEmpty) ...[
const Text("Today"),
renderCardsList(elementsForToday),
],
if (elementsFromTheFuture.isNotEmpty) ...[
const Text("Future"),
renderCardsList(elementsFromTheFuture),
],
],
),
);
}
}
/// Mock model.
class MyObject {
const MyObject({required this.id, required this.name, required this.date});
final int id;
final String name;
final DateTime date;
}
List<MyObject> getMockList() {
final today = DateTime.now().removeTimeComponents();
final yesterday = today.subtract(const Duration(days: 1));
final tomorrow = today.add(const Duration(days: 1));
return [
MyObject(id: 0, name: "1", date: yesterday),
MyObject(id: 1, name: "2", date: today),
MyObject(id: 2, name: "3", date: yesterday),
MyObject(id: 3, name: "4", date: tomorrow),
MyObject(id: 4, name: "5", date: today),
MyObject(id: 5, name: "6", date: tomorrow),
MyObject(id: 6, name: "7", date: tomorrow),
];
}
/// Mock card to display [MyObject].
class MyCard extends StatelessWidget {
const MyCard({super.key, required this.object});
final MyObject object;
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(flex: 1, child: Text(object.name)),
Expanded(flex: 3, child: Text(object.date.toString())),
],
);
}
}
/// Mock list view to display [MyCard]s.
///
/// Mock data will be used to populate the list, you can replace it with your
/// own.
ListView renderCardsList(List<MyObject> list) {
return ListView.builder(
/// When 'shrinkWrap' is set to true, the list will only be as big as its
/// children, instead of attempting to fill the available space.
shrinkWrap: true,
itemCount: list.length,
itemBuilder: (context, index) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 5.0),
child: MyCard(object: list[index]),
),
);
}