skip to content

A Flutter nutrition-label Row with an Expanded bar throws 'RenderFlex children have non-zero flex but incoming width constraints are unbounded'; why, and how do you fix it?

level: seniorimportance: should knowfreq 45%

answer

  1. expand versus shrink-wrap contradiction
  2. infinite main-axis maximum
  3. nearest ancestor giving unbounded width
  4. inner Row inside an outer Row
  5. min size plus loose fit

basics

~20 s

The Row sits where its width is unbounded, such as a horizontal scroll view or a non-flexible slot of another Row, so an Expanded child has no finite leftover width to fill. Bound the Row's width, typically by making the Row itself flexible in its parent.

solid answer

~50 s

`Expanded` means 'fill the space left after the inflexible children'. If the Row's incoming max width is infinite, that space is infinite. The Row would have to shrink-wrap while the child expands, and in debug the framework reports the contradiction. The report names 'the nearest ancestor providing an unbounded width constraint', which is where I look first. Typical culprits: a horizontal `SingleChildScrollView`, or the nutrient Row nested as an ordinary child of an outer Row, which lays non-flex children out with unbounded width. Fixes, by intent: wrap the inner Row in `Expanded` in the outer Row; give it a fixed width; or remove the horizontal scroll. The hint to use `MainAxisSize.min` with `FlexFit.loose` silences this check, but under unbounded width flexible children are laid out at their natural size, and a `LinearProgressIndicator` then fails with 'BoxConstraints forces an infinite width.', so the bar still needs a real width.

code

dart · 26 lines
dart
import 'package:flutter/material.dart';

Widget nutrientRow(double percent) => Row(
      children: [
        const Text('Sodium'),
        Expanded(child: LinearProgressIndicator(value: percent)),
        const Text('460 mg'),
      ],
    );

// Throws: the details Column is an inflexible child of the outer Row,
// so each nutrientRow receives an unbounded width.
Widget broken(double percent) => Row(
      children: [
        const Icon(Icons.fastfood),
        Column(children: [nutrientRow(percent)]),
      ],
    );

// Fixed: the details Column takes the outer Row's remaining width.
Widget fixed(double percent) => Row(
      children: [
        const Icon(Icons.fastfood),
        Expanded(child: Column(children: [nutrientRow(percent)])),
      ],
    );

go deeper

for a junior

Recall that Expanded needs a Row with a finite width, and that horizontal scroll views and nested Rows remove that limit.

for a middle

Explain why an infinite maximum makes 'fill the remaining space' contradictory, and why a Row nested in another Row has an unbounded width.

for a senior

Locate the unbounded ancestor from the error, bound the width at the right level, and explain why the framework's loose-fit hint silences the error without restoring flex behaviour.

for a principal

Set review rules for reusable row components, such as never assuming a bounded width and documenting where flex children are allowed, so these errors do not reach feature teams.

## The error and what it means The full debug message starts with **'RenderFlex children have non-zero flex but incoming width constraints are unbounded.'** and explains that when a row is in a parent without a finite width, 'for example if it is in a horizontal scrollable, it will try to shrink-wrap its children along the horizontal axis', while a flex factor 'indicates that the child is to expand to fill the remaining space'. The framework's own summary: 'These two directives are mutually exclusive.' In constraint terms: - A flexible child's share is the Row's max width minus the inflexible children, divided by the total flex. - If the max width is `double.infinity`, the share is infinite. - A tight fit (`Expanded`) would force the child to be infinitely wide, which cannot be laid out. The check fires when the main axis is unbounded **and** either the Row uses `MainAxisSize.max` or the flex child's fit is tight. The `Column` version says 'height' instead of 'width'. ## Finding the unbounded ancestor The message includes **'The nearest ancestor providing an unbounded width constraint is: …'** followed by that render object and its creator. Common ones: 1. **A horizontal `SingleChildScrollView`** or other horizontal scrollable around the Row. Its content gets an unbounded width by design. 2. **An outer `Row`** that has the nutrient Row as an ordinary child. A Row lays out its inflexible children with an unbounded main axis, so a Row nested in a Row gets infinite width. 3. **`UnconstrainedBox`** or `OverflowBox`, which pass unbounded constraints on purpose. The vertical twin is a `Column` using `Expanded` or `Spacer` inside a vertical `SingleChildScrollView`. ## Fixes, chosen by intent | Intent | Fix | |---|---| | The nutrient row should take the rest of an outer Row | Wrap the inner Row in `Expanded` (or `Flexible`) in the outer Row, so it receives a bounded share | | The row should be a known width | Wrap it in `SizedBox(width: …)` or give its parent a bounded width | | The horizontal scroll was accidental | Remove it; the Row then gets the screen's bounded width | | The row really must scroll horizontally | Drop the flex children and give the bar an explicit width, because 'the rest of an infinite width' is meaningless | The error also carries a hint: consider `MainAxisSize.min` with `FlexFit.loose` fits, that is, `Flexible` rather than `Expanded`. That satisfies the check, but it is worth knowing why. When the main axis is unbounded, `RenderFlex` does not run the flex step at all: it lays out every child, flexible or not, with the unbounded main axis. A `Flexible` text is then just a text at its natural width. A `LinearProgressIndicator` bar asks for an infinite minimum width (it is built to fill whatever width it is given), so under an unbounded width it fails with a different error, 'BoxConstraints forces an infinite width.' The hint removes the flex error; it does not bring back 'fill the remaining space'. ## Walking through the nutrition label Suppose the label has an outer Row of a thumbnail and a details Column, and each nutrient Row (name, `Expanded` bar, amount) lives in that Column: - The details Column is an inflexible child of the outer Row, so its width is unbounded. - The Column passes that unbounded width to each nutrient Row. - The nutrient Row's `Expanded` bar trips the error. The fix is one wrapper: `Expanded(child: detailsColumn)` in the outer Row. The Column now receives 'the outer Row's width minus the thumbnail', every nutrient Row inside it is bounded, and the bar fills its share. ## How to answer in an interview 1. State the contradiction: shrink-wrap versus expand under an infinite maximum. 2. Name the usual sources: horizontal scrollables and nested Rows. 3. Use the error's 'nearest ancestor' line to locate it. 4. Fix by bounding the width at the right level, usually by making the nested Row itself flexible, and explain why the loose-fit hint does not make a bar fill anything.

  • Why does a Flutter Row inside a vertical SingleChildScrollView not hit this error, while one inside a horizontal scroll view does?
    A vertical scroll view leaves the height unbounded but still passes the screen's bounded width to its content, so a Row's main axis, its width, stays finite and `Expanded` has a real share. A horizontal scroll view makes the width unbounded, which is the Row's main axis, so any tight flex child hits the error.
  • If you follow the error's hint and switch to MainAxisSize.min with Flexible, what does the bar look like?
    Under an unbounded main axis the Row skips the flex step and lays out every child, flexible ones included, with an unbounded width. The name and amount texts take their natural widths, but a `LinearProgressIndicator` asks for an infinite minimum width, so it now fails with 'BoxConstraints forces an infinite width.' Give the bar an explicit width, or bound the Row instead.

saying these in an interview costs you the question

  • Expanded cannot be used inside any scrollable widget.
  • Increasing the flex factor fixes the unbounded-width error.
  • Wrapping the nested Row in Center gives it a bounded width.
  • The loose-fit hint makes flexible children fill the remaining width again.
  • A Row nested inside a Row automatically gets the outer Row's width.