Most everyday Flutter errors are the layout contract being broken: a child that is too big for a Row or Column (RenderFlex overflowed), a scrollable or text field that was given no limit (unbounded height/width), or a widget like Expanded placed under the wrong parent. The remaining classics, setState called during build and RenderBox was not laid out, are a side effect in build() and a symptom of an earlier error respectively. Read the first error in the console, give the widget a constraint, and the rest usually disappears.
- Layout is a negotiation: parents pass constraints down, children pick a size within them. Nearly every red-screen layout error names the side that broke the deal.
- 'A RenderFlex overflowed by N pixels': a Row/Column child wants more space than the flex can give. Wrap it in Expanded or Flexible, let text ellipsize, or make the content scroll.
- 'Vertical viewport was given unbounded height' and 'An InputDecorator ... cannot have an unbounded width': a ListView inside a Column, or a TextField inside a Row, with nothing bounding the main axis. Give it a size with Expanded or SizedBox.
- 'Incorrect use of ParentDataWidget': Expanded/Flexible must be direct children of Row/Column/Flex, Positioned of Stack, TableCell of Table.
- 'setState() or markNeedsBuild() called during build': build() must not call setState, showDialog, Navigator.push or anything that marks widgets dirty. Move it to an event handler, initState with addPostFrameCallback, or push the dialog as its own route.
- 'RenderBox was not laid out' and the red (debug) or grey (release) screen are symptoms; fix the first exception in the log. Customize what users see with ErrorWidget.builder, report framework errors from FlutterError.onError and everything else from PlatformDispatcher.instance.onError.
| Error message | Typical cause | Fix |
|---|---|---|
| A RenderFlex overflowed by N pixels on the right/bottom | A Row or Column child (often a long Text or an unconstrained Column) asks for more space than the flex has | Wrap the child in Expanded or Flexible; set overflow: TextOverflow.ellipsis or softWrap; use Wrap or a scroll view when content can legitimately exceed the space |
| Vertical viewport was given unbounded height | ListView, GridView or another scrollable placed in a Column (or nested in another scrollable) with no height limit | Wrap the scrollable in Expanded or Flexible, give it a SizedBox height, or for a short fixed list use shrinkWrap: true with NeverScrollableScrollPhysics; for scroll-in-scroll use slivers in one CustomScrollView |
| An InputDecorator, which is typically created by a TextField, cannot have an unbounded width | TextField or TextFormField placed directly in a Row | Wrap the field in Expanded or SizedBox(width: ...) |
| Incorrect use of ParentDataWidget | Expanded/Flexible not directly under Row/Column/Flex, Positioned outside Stack, TableCell outside Table | Move the widget so its direct parent is the expected one, or remove it; a Padding or Container between them also breaks it |
| setState() or markNeedsBuild() called during build | setState, showDialog, Navigator.push or a notifier change triggered synchronously inside build() | Run it from an event handler, from initState via WidgetsBinding.instance.addPostFrameCallback, or push the dialog as a route when navigating to the page |
| RenderBox was not laid out | A render object was painted or measured before layout completed, almost always because one of the constraint errors above happened first | Fix the earlier error in the console; this one disappears with it |
| Red screen (debug/profile) or grey screen (release) | An uncaught exception during build, layout or paint; ErrorWidget replaced the failed subtree | Read the first exception, add handling or constraints; override ErrorWidget.builder for a friendlier placeholder |
| The ScrollController is attached to multiple scroll views | Several scrollables share one controller, often the PrimaryScrollController on web/desktop | Give each scrollable its own ScrollController or set primary: false on the extra ones |
The messages are the first line Flutter prints; the rest of each error explains which widget triggered it, so scroll up to the first assertion rather than the last.
Layout as a lease negotiation
A parent widget is a landlord offering a space with limits: at most this wide, at most this tall (sometimes 'as tall as you like'). The child is a tenant who must choose a size inside those limits and report it back. An overflow is a tenant who builds past the property line; the yellow-and-black stripes mark the trespass. An unbounded-height error is a landlord who said 'as tall as you like' to a tenant (a ListView) that only knows how to fill whatever height it is given, so it cannot answer. Expanded is the landlord assigning the tenant a definite share of the remaining space.
Minimal working example
import 'package:flutter/material.dart';
class InboxPage extends StatelessWidget {
const InboxPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Inbox')),
body: Column(
children: [
const Padding(
padding: EdgeInsets.all(16),
child: Row(
children: [
Icon(Icons.inbox),
SizedBox(width: 8),
Expanded(
child: Text(
'A long subject line that would otherwise overflow the row',
overflow: TextOverflow.ellipsis,
),
),
],
),
),
Expanded(
child: ListView.builder(
itemCount: 50,
itemBuilder: (context, index) => ListTile(
title: Text('Message $index'),
),
),
),
],
),
);
}
}- L14A Row gives its children unbounded width on the main axis unless they are flex children.
- L18Expanded makes the Text a flex child: it receives the remaining width as a tight constraint, so it can never overflow the Row.
- L21With a bounded width the Text can wrap or, as here, truncate instead of painting past the edge.
- L27A Column gives children unbounded height; Expanded gives the ListView the remaining height instead, which fixes 'Vertical viewport was given unbounded height'.
- L28ListView.builder lays out only the visible items; it still needs a bounded height to know what 'visible' means.
Use cases
- Yellow-and-black stripes appear at the edge of a Row or Column and the console says a RenderFlex overflowed.
- A ListView, GridView or TextField inside a Column or Row makes the whole screen fail with 'unbounded height' or 'unbounded width'.
- A dialog, snackbar or navigation that you trigger from build() throws 'setState() or markNeedsBuild() called during build'.
- Users report a grey screen in release and you need to know what happened and show something better than a blank box.
Benefits
- Each message maps to one missing constraint or one misplaced widget, so the fix is usually a single wrapper such as Expanded or SizedBox.
- Understanding the first error avoids chasing its symptoms (RenderBox was not laid out, blank screens) one by one.
- Centralized error handlers give you crash reports from release builds, where the red screen is never shown.
Limitations
- These fixes assume the box layout protocol; inside a CustomScrollView the equivalents are sliver widgets (SliverList, SliverFillRemaining), not Expanded.
- shrinkWrap: true makes a scrollable size itself to all of its children, which defeats lazy building and is slow for long lists; it is a fix for short lists only.
- FlutterError.onError and PlatformDispatcher.onError only see errors in the main isolate; a spawned isolate needs its own error port handling.
Alternatives and trade-offs
- Understanding constraints — You keep hitting unbounded or overflow errors and want the model behind 'constraints go down, sizes go up' rather than one fix at a time.
- Widget tests — Layout assertions throw during pumpWidget, so a test per screen catches overflow and unbounded errors before a device ever shows them.
- Flutter inspector and DevTools — You can see the overflow but cannot tell which widget is responsible; the inspector's layout explorer shows constraints and sizes per node.
- State management patterns — The setState-during-build error comes from a notifier or stream firing while widgets rebuild; restructuring who owns the state removes the cause.
'A RenderFlex overflowed': a Column of text inside a Row
Problem: The Row offers unbounded width to a non-flex child; the inner Column sizes itself to its longest Text, which is wider than the screen, so the Row paints past its edge and reports the overflow.
Fix: Make the Column a flex child with Expanded (or Flexible with a flex factor) so it receives the remaining width as its constraint.
Row(
children: [
const Icon(Icons.message),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text('Title'),
Text('A very long body that is wider than the screen ...'),
],
),
],
)Row(
children: [
const Icon(Icons.message),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text('Title'),
Text('A very long body that is wider than the screen ...'),
],
),
),
],
)'Vertical viewport was given unbounded height': a ListView inside a Column
Problem: A Column does not limit its children's height and a ListView expands to fill whatever height it gets, so the viewport is asked to be infinitely tall and the assertion fires (usually followed by 'RenderBox was not laid out').
Fix: Wrap the ListView in Expanded to take the remaining height, or give it an explicit SizedBox height. For a short, fixed list that should scroll with the page, use shrinkWrap: true plus NeverScrollableScrollPhysics inside an outer scroll view.
Column(
children: [
const Text('Header'),
ListView(
children: const [
ListTile(title: Text('Map')),
ListTile(title: Text('Subway')),
],
),
],
)Column(
children: [
const Text('Header'),
Expanded(
child: ListView(
children: const [
ListTile(title: Text('Map')),
ListTile(title: Text('Subway')),
],
),
),
],
)'An InputDecorator ... cannot have an unbounded width': a TextField inside a Row
Problem: The decorated text field needs a finite width to lay out its border, label and content; a Row offers it unbounded width.
Fix: Wrap the field in Expanded (share the row) or SizedBox(width: ...) (fixed width).
Row(children: const [TextField()])Row(children: [Expanded(child: TextFormField())])'setState() called during build': opening a dialog from build()
Problem: showDialog pushes a route, which marks the Navigator and Overlay dirty while the framework is still building; build can run every frame, so even if it worked it would open the dialog repeatedly.
Fix: Trigger the dialog from an event (a button) or, if it must appear on entry, schedule it after the first frame in initState, or push it as a second route right after navigating to the page.
@override
Widget build(BuildContext context) {
showDialog<void>(
context: context,
builder: (_) => const AlertDialog(title: Text('Alert')),
);
return const Center(child: Text('Page'));
}@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return;
showDialog<void>(
context: context,
builder: (_) => const AlertDialog(title: Text('Alert')),
);
});
}This page is Boring Doc’s independent explanation. For the complete specification use the official pages below (content © The Flutter Authors, CC BY 3.0; code samples BSD). Dates show when the explanation was last checked and when the official page last changed.
- https://docs.flutter.dev/testing/common-errors
- Last checked
- Oct 21, 2026
- Source last updated
- 3 weeks ago
- Last synchronized
- 3 weeks ago
- https://docs.flutter.dev/testing/debugging
- Last checked
- Oct 21, 2026
- Source last updated
- 3 weeks ago
- Last synchronized
- 3 weeks ago
- https://docs.flutter.dev/testing/errors
- Last checked
- Oct 21, 2026
- Source last updated
- 3 weeks ago
- Last synchronized
- 3 weeks ago
- FlutterError class API reference Flutter 3.47.7https://api.flutter.dev/flutter/foundation/FlutterError-class.html
- Last checked
- Oct 21, 2026
AI_PROVIDER (for example to openai-compatible with a local Ollama or LM Studio endpoint) to enable grounded explanations. Everything else on this page works without an AI provider.