Beginner · Lesson 10 · 11 min read
Debugging: Reading Errors and Using DevTools
Decode Flutter's most common error messages, use breakpoints and the widget inspector, and build a repeatable approach to fixing bugs.
Updated August 1, 2026
What you will learn
- Read a Flutter error message and find the real cause
- Recognise and fix the most common runtime errors
- Use breakpoints and the DevTools widget inspector
- Log usefully without slowing down release builds
Flutter's error messages are unusually good — they often tell you exactly which widget failed and suggest a fix. The skill is knowing where to look in a wall of red text, and that skill is mostly pattern recognition.
How to read an error
Flutter frames every error the same way. Read it in this order:
- The first line names the exception type and the short reason. This is usually enough.
- "The relevant error-causing widget was" tells you which of your widgets triggered it — skip past framework frames to find this.
- The stack trace, read top-down, looking for the first line pointing at a file in your
lib/folder. That is where to put a breakpoint. - The suggestions at the bottom. Flutter frequently names the exact fix.
The errors you will actually hit
| Message | Cause | Fix |
|---|---|---|
A RenderFlex overflowed by N pixels | A child wanted more space than its Row/Column had | Wrap the child in Expanded/Flexible, or make the parent scrollable |
Vertical viewport was given unbounded height | A ListView inside a Column | Wrap the ListView in Expanded, or give it a fixed height |
setState() called after dispose() | An async callback returned after the widget was removed | if (!mounted) return; before the setState |
setState() or markNeedsBuild() called during build | State mutated while building | Move the call into a callback or initState |
Null check operator used on a null value | A ! on something that was null | Use ?./??, or find why the value is missing |
No Material widget found | A Material widget outside a Scaffold/Material | Wrap it in Material or Scaffold |
Unable to load asset | Asset missing from pubspec, or a typo in the path | Declare it under flutter: assets:, then full restart |
Incorrect use of ParentDataWidget | Expanded/Positioned used outside Flex/Stack | Only use them as direct children of the right parent |
Print debugging, done properly
import 'package:flutter/foundation.dart';
// debugPrint throttles output so Android does not drop lines
debugPrint('Loaded ${articles.length} articles');
// kDebugMode is a compile-time constant — this whole block is
// removed from release builds by tree shaking
if (kDebugMode) {
debugPrint('Auth token: $token');
}
// Structured logging, filterable by name in DevTools
import 'dart:developer' as developer;
developer.log(
'Fetch failed',
name: 'api.articles',
error: error,
stackTrace: stackTrace,
);Breakpoints beat print statements
- Click the gutter next to a line in VS Code or Android Studio and run in debug mode. Execution pauses and you can inspect every variable in scope.
- Conditional breakpoints — right-click a breakpoint and add an expression like
index == 47to stop only on the case that misbehaves. - Step over / step into / step out move through code one line at a time; the call stack panel shows how you arrived.
- Add an expression to the Watch panel to see it update as you step.
// Assertions run only in debug builds — a cheap way to catch bad state early
assert(items.isNotEmpty, 'Items must not be empty when building the list');
// Pause the debugger from code, at exactly the moment you care about
import 'dart:developer';
if (article.id == suspiciousId) {
debugger(); // execution stops here when debugging
}DevTools
DevTools opens automatically from the URL printed by flutter run, or through your IDE's Flutter panel. The tabs you will use most as a beginner:
- Widget Inspector — click any widget on screen and jump to the code that built it. Its layout view shows the constraints and size of every box, which makes overflow bugs obvious.
- Debug Console — your logs, grouped and filterable.
- Network — every HTTP request with headers, timing and response body.
- Performance — frame times, for when scrolling feels rough.
- Memory — object counts over time, for hunting leaks.
import 'package:flutter/rendering.dart';
void main() {
// Outlines every render box — instant visual layout debugging
debugPaintSizeEnabled = true;
// Highlights tap targets
debugPaintPointersEnabled = true;
runApp(const MyApp());
}A repeatable method
- Reproduce it reliably. A bug you cannot trigger on demand cannot be verified as fixed.
- Narrow it down. Comment out half the screen. Does it still happen? Repeat. This finds the culprit faster than reading code.
- Read the actual error, not the first line of red you see. Scroll up — the real cause is often above the noise.
- Form one hypothesis and test it. Changing four things at once means you will not know which fixed it.
- Check the obvious things: is it a hot-reload artifact? Try a hot restart. Still there?
flutter clean && flutter pub get. - Write a test that fails on the bug once you find it, so it cannot come back silently.
Key takeaways
- Find "the relevant error-causing widget" line — it names your code, not the framework's.
- Most beginner errors are one of about eight patterns; learn them and the fixes are instant.
- Use
debugPrintandkDebugMode, never rawprintwith sensitive values. - The DevTools widget inspector shows real constraints and sizes — it ends layout guesswork.
Practice
Break it on purpose
Deliberately create four errors: a Row overflow, a ListView in a Column, a setState after dispose, and a null check on a null value. For each, read the message, write down which line told you the cause, then fix it. You will recognise all four instantly afterwards.
Show hints
- For setState-after-dispose, await a long Future then setState, and navigate away while it runs.
- Turn on
debugPaintSizeEnabledwhile fixing the overflow to see the boxes involved.