Debugging & DevTools

Every non-trivial app eventually misbehaves, and Flutter gives you a genuinely strong set of tools for figuring out why: simple console logging, a full browser-based suite called Flutter DevTools, a Widget Inspector for visually exploring your running app, and your IDE's regular debugger. This lesson walks through each, plus one of the most common real bugs you'll actually run into — a layout overflow — and how these tools help you fix it.

Quick Console Debugging: print() vs debugPrint()

The simplest debugging tool is printing a value to the console. Dart's built-in print() works, but Flutter also provides debugPrint(), which is usually the better default.

Example: Logging a Value During Development

final List<String> items = fetchLongItemList();
debugPrint('Fetched ${items.length} items: $items');

The reason debugPrint() is often preferred over plain print() is a practical one: Android's system log truncates very long single lines of output, silently cutting off large messages. debugPrint() automatically throttles and batches large output so it isn't truncated, which matters more than it sounds the first time you print a large JSON object or a long list and can't figure out why the end of your log line is missing.

Flutter DevTools: A Suite of Tools in Your Browser

Flutter DevTools is a collection of performance and debugging tools that open in a regular browser tab while your app runs on a device or emulator. VS Code and Android Studio launch it automatically the moment you start a debug session — look for a "DevTools" or "Open DevTools" option in the debug toolbar. It can also be launched manually from a terminal:

flutter pub global activate devtools
flutter pub global run devtools

DevTools includes panes for the widget tree, performance timing, memory usage, network requests, and logging — all connected live to your running app, updating as you interact with it.

The Widget Inspector: Exploring Your Running App Visually

The Widget Inspector pane inside DevTools shows the entire widget tree of your currently running app, and lets you click any widget directly on the device screen to jump straight to its position in that tree — showing its type, its properties, and its exact layout constraints. This is genuinely useful for the layout questions covered in Layout Widgets — instead of guessing why a Row or Column is sized the way it is, you can select it live and read its actual constraints and size.

Debug Paint: Visualizing Every Widget's Boundaries

A related toggle, "debug paint" (available as a button in DevTools, or by setting debugPaintSizeEnabled = true in code), overlays a colored border and padding indicator around every widget currently on screen. It turns an otherwise invisible layout into something you can literally see — useful for spotting unexpected padding, a widget that's larger or smaller than you assumed, or overlapping elements that are hard to reason about from code alone.

Breakpoints and Stepping Through Code

Beyond Flutter-specific tools, ordinary debugger usage still applies: set a breakpoint on a line in VS Code or Android Studio by clicking the gutter next to it, then run your app in debug mode. Execution pauses at that line, letting you inspect variable values, step line by line, and step into or over function calls — the same debugging workflow you'd use in most other languages, now connected to your live Flutter app.

A Common Real Bug: RenderFlex Overflowed

One of the most common errors a Flutter beginner runs into is a bright yellow-and-black striped banner in the app, paired with a console error like:

A RenderFlex overflowed by 42 pixels on the right.

This happens when a Row (or Column) tries to lay out children whose combined natural width is wider than the space actually available — commonly a Row containing a long piece of text plus an icon, where the text alone is already wider than the screen. The Widget Inspector immediately identifies which specific Row or Column is overflowing by highlighting it in the tree. The two most common fixes, both covered in Layout Widgets, are wrapping the oversized child in Expanded (so it shrinks to fit the available space) or wrapping the whole row in a SingleChildScrollView (so it scrolls instead of overflowing) when the content genuinely doesn't fit on screen at once.

Example: Fixing an Overflowing Row With Expanded

// Before: long text can overflow a narrow Row
Row(
  children: [
    Icon(Icons.info),
    Text('A surprisingly long label that does not fit'),
  ],
)

// After: Expanded lets the text wrap/shrink within the remaining space
Row(
  children: [
    Icon(Icons.info),
    Expanded(
      child: Text('A surprisingly long label that does not fit'),
    ),
  ],
)
Tip: when you see a RenderFlex overflow error, the console message tells you the exact widget and file/line — read it first, then confirm visually with the Widget Inspector or debug paint before guessing at a fix.
Next up: head to Testing to learn how to write automated tests that catch bugs like this before they ever reach a user.