skip to Main Content

I have a scrollable list of checkboxes. When I realized that I could have more or that small screen size may not hold my list, I decided to use the simplest scrollable list (ListView).

If I shrinkwrap the list – no exceptions and the scroll is present but of course – you overrun the screen.

The exception says that the most likely call was embedded scrollable lists – but to my knowledge I don’t have that.

When I run the code, I get the following exception:

A RenderFlex overflowed by 399 pixels on the bottom.
The relevant error-causing widget was:
Column Column:file:///C:/u/bob/Home/flutter_apps/cart_provider/samples/provider_shopper/lib/main.dart:69:15

════════ Exception caught by rendering library ═════════════════════════════════
The following assertion was thrown during performResize():
Vertical viewport was given unbounded height.
Viewports expand in the scrolling direction to fill their container. In this case, a vertical viewport was given an unlimited amount of vertical space in which to expand. This situation typically happens when a scrollable widget is nested inside another scrollable widget.
If this widget is always nested in a scrollable widget there is no need to use a viewport because there will always be enough vertical space for the children. In this case, consider using a Column or Wrap instead. Otherwise, consider using a CustomScrollView to concatenate arbitrary slivers into a single scrollable.

The relevant error-causing widget was:
ListView ListView:file:///C:/u/bob/Home/flutter_apps/cart_provider/samples/provider_shopper/lib/main.dart:132:9

When the exception was thrown, this was the stack:
#0 debugCheckHasBoundedAxis. (package:flutter/src/rendering/debug.dart:337:13)
#1 debugCheckHasBoundedAxis (package:flutter/src/rendering/debug.dart:396:4)
#2 RenderViewport.computeDryLayout (package:flutter/src/rendering/viewport.dart:1384:12)
#3 RenderBox.performResize (package:flutter/src/rendering/box.dart:2668:12)
#4 RenderObject.layout (package:flutter/src/rendering/object.dart:2587:9)
#5 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:111:21)
#6 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#7 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:111:21)
#8 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
… more of the same

(elided 3 frames from class _Timer and dart:async-patch)

The following RenderObject was being processed when the exception was fired: RenderViewport#5a642 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
needs compositing
parentData: (can use size)
constraints: BoxConstraints(0.0<=w<=384.0, 0.0<=h<=Infinity)
size: MISSING
axisDirection: down
crossAxisDirection: right
offset: ScrollPositionWithSingleContext#f4a53(offset: 0.0, range: 0.0..0.0, viewport: 1056.0, ScrollableState, AlwaysScrollableScrollPhysics -> ClampingScrollPhysics -> RangeMaintainingScrollPhysics, IdleScrollActivity#d1005, ScrollDirection.idle)
anchor: 0.0
center child: RenderSliverPadding#74a1d NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
parentData: paintOffset=Offset(0.0, 0.0)
constraints: MISSING
geometry: null
padding: EdgeInsets.zero
textDirection: ltr
child: RenderSliverList#7879c NEEDS-LAYOUT NEEDS-PAINT
parentData: paintOffset=Offset(0.0, 0.0)
constraints: MISSING
geometry: null
no children current live
RenderObject: RenderViewport#5a642 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
needs compositing
parentData: (can use size)
constraints: BoxConstraints(0.0<=w<=384.0, 0.0<=h<=Infinity)
size: MISSING
axisDirection: down
crossAxisDirection: right
offset: ScrollPositionWithSingleContext#f4a53(offset: 0.0, range: 0.0..0.0, viewport: 1056.0, ScrollableState, AlwaysScrollableScrollPhysics -> ClampingScrollPhysics -> RangeMaintainingScrollPhysics, IdleScrollActivity#d1005, ScrollDirection.idle)
anchor: 0.0
center child: RenderSliverPadding#74a1d NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
parentData: paintOffset=Offset(0.0, 0.0)
constraints: MISSING
geometry: null
padding: EdgeInsets.zero
textDirection: ltr
child: RenderSliverList#7879c NEEDS-LAYOUT NEEDS-PAINT
parentData: paintOffset=Offset(0.0, 0.0)
constraints: MISSING
geometry: null
no children current live
════════════════════════════════════════════════════════════════════════════════

Here is the code:

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Circuit Cellar!',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const CCArticles(),
    );
  }
}

class CCArticles extends StatefulWidget {
  const CCArticles({super.key});

  @override
  State<CCArticles> createState() => _CCArticleState();
}

class _CCArticleState extends State<CCArticles> {
  int completedTasks = 0;

  void updateProgress(bool isChecked) {
    setState(() {
      completedTasks += isChecked ? 1 : -1;
    });
  }

  @override
  Widget build(BuildContext context) {
    List<Widget> tasks = [
      const TaskItem(label: 'Embedded in Thin Slices'),
      const TaskItem(label: 'Technology Features'),
      const TaskItem(label: 'Datasheets'),
      const TaskItem(label: 'From the Bench'),
      const TaskItem(label: 'Picking Up Mixed Signals'),
      const TaskItem(label: 'Embedded Systems Essentials'),
      const TaskItem(label: 'The Magic Smoke Factory'),
      const TaskItem(label: 'Product News'),
      const TaskItem(label: 'Feature Article #1'),
      const TaskItem(label: 'Feature Article #2'),
      const TaskItem(label: 'Feature Article #13'),
      const TaskItem(label: 'Feature Article #14'),
      const TaskItem(label: 'Feature Article #3'),
      const TaskItem(label: 'Feature Article #4'),
      const TaskItem(label: 'Feature Article #5'),
      const TaskItem(label: 'Feature Article #6'),
      const TaskItem(label: 'Feature Article #7'),
      const TaskItem(label: 'Feature Article #8'),
      const TaskItem(label: 'Feature Article #9'),
      const TaskItem(label: 'Feature Article #10'),
      const TaskItem(label: 'Feature Article #11'),
      const TaskItem(label: 'Feature Article #12'),
    ];

    return TaskInherited(
      completedTasks: completedTasks,
      totalTasks: tasks.length,
      updateProgress: updateProgress,
      child: Scaffold(
        appBar: AppBar(
          title: const Text('Circuit Cellar Reading Planner!'),
        ),
        body: Column(
          children: [
            const Progress(),
            TaskList(tasks: tasks),
          ],
        ),
      ),
    );
  }
}

class TaskInherited extends InheritedWidget {
  final int completedTasks;
  final int totalTasks;
  final void Function(bool) updateProgress;

  const TaskInherited({
    super.key,
    required this.completedTasks,
    required this.totalTasks,
    required this.updateProgress,
    required super.child,
  });

  static TaskInherited of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<TaskInherited>()!;
  }

  @override
  bool updateShouldNotify(TaskInherited oldWidget) {
    return completedTasks != oldWidget.completedTasks;
  }
}

class Progress extends StatelessWidget {
  const Progress({super.key});

  @override
  Widget build(BuildContext context) {
    final inheritedData = TaskInherited.of(context);

    double progress = inheritedData.completedTasks / inheritedData.totalTasks;

    return Column(
      children: [
        const Text('Reading Progress:'),
        LinearProgressIndicator(
            value: progress,
            borderRadius: BorderRadius.circular(4.0),
            minHeight: 30.0),
        Text('${(progress * 100).toStringAsFixed(0)}% completed'),
      ],
    );
  }
}

class TaskList extends StatelessWidget {
  const TaskList({super.key, required this.tasks});
  final List<Widget> tasks;
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ListView(
          padding: const EdgeInsets.symmetric(),
//          shrinkWrap: true,
          children: tasks,
        ),
      ],
    );
  }
}

class TaskItem extends StatefulWidget {
  final String label;

  const TaskItem({super.key, required this.label});

  @override
  TaskItemState createState() => TaskItemState();
}

class TaskItemState extends State<TaskItem> {
  bool _isChecked = false;

  @override
  Widget build(BuildContext context) {
    final inheritedData = TaskInherited.of(context);

    return Row(
      children: [
        Checkbox(
          onChanged: (newValue) {
            setState(() {
              _isChecked = newValue!;
              inheritedData.updateProgress(_isChecked);
            });
          },
          value: _isChecked,
        ),
        Text(widget.label),
      ],
    );
  }
}

2

Answers


  1. Chosen as BEST ANSWER

    After two hours, I decided to finally ask all of you. Within 5 minutes I found that wrapping it with Flexible eliminates the error.

    Here is the code:

      Widget build(BuildContext context) {
        return Flexible(
          child: ListView(
            padding: const EdgeInsets.symmetric(),
            children: tasks,
          ),
        );
      }
    

    Md. Yeasin Sheikh also suggested just a minute later: wrapping it with Expanded and that works too. Not sure what I missed in the documentation about ListView.


  2. In your home screen (CCArticles) your build is built like this:

    body: Column(
              children: [
                const Progress(),
                TaskList(tasks: tasks),],),
    

    Where TaskList returns a scrollable widget.

    A scrolling widget inside a Column without constraints will attempt to expand infinitely, leading to a layout overflow error because a Column widget allows its children to take up as much vertical space as needed without considering the screen size.

    Since a scrolling widget tries to expand to fit all its contents, it will expand the column and overflow vertically, which is why you need a height constraint. Even if you have shrinkWrap: true, the number of items are too big that they are still expanding past the screen size.

    A better solution is to wrap the Column in a scrollable widget, and disable the scroll of the ListView to avoid clashing scrolls, by adding physics: NeverScrollableScrollPhysics()

    body: SingleChildScrollView(
              child: Column(
                children: [
                  const Progress(),
                  TaskList(tasks: tasks),
                ],
              ),
            ),
    
    class TaskList extends StatelessWidget {
      const TaskList({super.key, required this.tasks});
      final List<Widget> tasks;
      @override
      Widget build(BuildContext context) {
        return ListView(
          padding: const EdgeInsets.symmetric(),
          shrinkWrap: true,
          physics: NeverScrollableScrollPhysics(),
          children: tasks,
        );
      }
    }
    

    If you still want to use the Column widget as the main parent, you can wrap TaskList with a SizedBox widget to give it a height constraint that won’t allow it to overflow vertically

    Column(
      children: [
        const Progress(),
        SizedBox(height: 200, child: TaskList(tasks: tasks),],),
    

    but since there are multiple devices with different screen sizes, it’s more practical to use an Expanded widget. Expanded widget fills up the rest of the available screen space.

    Column(
          children: [
            const Progress(),
            Expanded(child: TaskList(tasks: tasks),],),
    

    If you are to add more widgets after TaskList, you can place them in another Expanded widget and adjust the flex ratio parameter between the two.

    You can find more info about how to manage space in flutter documentation (layout Sizing Widgets section or Column Widget)

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search