Debugging Flutter Apps with DevTools: A Practical Tour
A practical tour of Flutter DevTools - Inspector, Performance, CPU Profiler, Memory, Network, and the debugger - with a workflow for hunting jank and leaks.
Published on • October 6, 2026
AI Assistant

print() debugging gets you through the morning. Real debugging - finding why frame 14,000 dropped, why memory climbs on the settings screen, which HTTP call takes 800ms - needs Flutter DevTools. It’s a suite of inspection tools that attaches to a running Flutter app (or test) and exposes the framework’s internals: the widget tree, the render pipeline, the isolate heap, the network stack.
Getting DevTools running
You rarely “launch” DevTools manually - your editor does it:
- VS Code: open the Run and Debug panel, start the app, then click the “Open DevTools” icon when it appears in the debug toolbar.
- Android Studio / IntelliJ: the DevTools buttons appear in the Debug tool window once the app connects.
- Command line: with an app running,
flutter devtoolsopens the suite in a browser and attaches to the connected device.
DevTools versions independently of the Flutter SDK - the docs call this out explicitly - so a mismatch usually means running dart pub global upgrade devtools. It works against debug and profile builds, and it can attach to widget tests, which is a workflow most developers discover too late.
The Inspector: what is actually on screen
The Flutter Inspector shows the live widget tree and the render objects behind it. Three habits pay off:
- Select on device. The widget-picking tool lets you tap the emulator and highlight the corresponding widget - start from the visual symptom, not the source file.
- Toggle Select Widget Mode. Rebuild counts and layout boundaries update as you interact, so you can see which interaction triggers work.
- Read the layout boundaries. A repaint boundary (shown when “Show Repaint Boundaries” is on) means Flutter captured that layer separately. Too many boundaries = memory; too few = over-drawing.
For state bugs, the Inspector’s “track widget rebuilds” feature (enabled from the track-rebuilds control) colors widgets by rebuild frequency - the fastest way to find a widget rebuilding on every animation tick.
The Performance view: jank forensics
Open Performance and reproduce the stutter. The UI thread and Raster thread timelines appear side by side:
- UI thread high: your Dart code is busy -
build()doing I/O, JSON decoding on the main isolate, excessive setState. - Raster thread high: the GPU pipeline is struggling - saveLayer calls, big blurs, full-screen opacity animations, or expensive shaders.
Practical workflow:
- Record, reproduce the jank, stop recording.
- Find the frame bar with a red/yellow spike above 16ms (or 8ms for 120fps targets).
- Click the frame to see its timeline events - look for
Layout,Build,Paint, andCompositechunks. - Attribute the hot chunk back to a widget using the stack trace the event carries.
Pair this with “Shader Compilation” tracking: the yellow shader-compilation bars are the modern version of the old shader warm-up jank. On Impeller-based builds (now the default across Android and desktop in 2026) most of this disappears - but the tool still confirms it.
The CPU Profiler: what your isolate is doing
When the UI thread is hot, switch to CPU Profiler and hit record. The flame chart and bottom-up views answer “where are my milliseconds going?”:
- A wide
buildframe → too much work per frame; split, cache, or push logic out of build. - Deep
json.decodestacks → move parsing to an isolate (compute()orIsolate.run). - Unexpected
setStatecascades → a state change is bubbling wider than intended; narrow withselect()orBlocSelector.
Sample-based profiling means a cold call occasionally lies - record at least a second of real interaction before drawing conclusions.
Memory: leaks before your users find them
Memory view tracks heap snapshots over time. The leak-hunting loop:
- Take a snapshot of a healthy screen.
- Navigate away, force garbage collection (the GC button), take another snapshot.
- Diff. Objects that should have been disposed but still exist are your leak.
Common Flutter leaks:
- Controllers never disposed -
AnimationController,TabController,TextEditingControllerheld by a StatefulWidget that outlives itsdispose()call. - Subscriptions - Streams and
ChangeNotifierlisteners added but never cancelled. Riverpod’s auto-dispose handles this; hand-rolled subscriptions don’t. - Closures capturing large graphs - a cached callback holding an entire widget subtree alive.
The Diff Snapshots table groups by class name - sort by retained size, and you’ll usually find one suspicious class dominating.
Network: slow calls, malformed payloads
The Network view records HTTP requests (dart:io and package:http traffic) with timing, status, and payload. Use it to:
- Confirm which endpoint fires on a screen - surprising duplicate calls show up immediately.
- Check response times against user-perceived latency budgets.
- Inspect payload shape when parsing throws - the response is often not what the model class expects.
For streaming APIs (SSE, chunked responses), the request detail shows incremental event timing.
Logging, Debugger, and the rest of the suite
- Logging:
debugPrintoutput plus framework logs, filterable by level and regex. Far better than scrolling console output. - Debugger: set breakpoints, step, inspect frames - a native-quality debugger for Dart, wired to your running app rather than a separate process.
- App Size:
flutter build apk --analyze-sizevisualized - find the asset or package bloating your download. - Deep Links: validates Android App Links / iOS Universal Links configuration.
- CPU profiler, Memory, Performance all support export/import, so you can attach findings to an issue ticket.
DevTools extensions are the newer addition: packages (drift, for example, or custom tooling) can ship their own DevTools tabs. If your stack has one, it replaces hand-rolled debug screens.
A repeatable debugging workflow
- Reproduce with the Performance view recording.
- Classify: UI thread, raster thread, or network - the timeline tells you.
- Drill down: CPU profiler for Dart hotspots, Memory for leaks, Network for slow I/O.
- Fix at the right layer - cache an expensive build, move work to an isolate, add a repaint boundary, cancel a subscription.
- Verify: record again, confirm the frame budget is back and the heap curve is flat.
Key Takeaways
- DevTools attaches from your IDE with one click - Inspector, Performance, CPU, Memory, Network, Logging, Debugger, and App Size in one suite.
- Jank triage starts with the Performance timeline: UI-thread hotspots go to the CPU profiler, raster hotspots to rendering costs.
- Leak hunting is snapshot-diffing - navigate, GC, diff, and chase the retained classes.
- Profile mode measures real performance; debug mode numbers are fiction.
- DevTools extensions let packages bring purpose-built panels into the same workflow.
References: