Most Flutter developers know how to build apps. Far fewer know how to build fast ones. The gap between a good app and a great one often isn't in the features. It's in the frames dropped, the memory leaked, and the milliseconds lost.
Dart DevTools is your toolkit for closing that gap. This guide walks through each of its major panels, what they're good for, and how to actually use them on a real app.
Before you start: Always profile in profile mode, not debug mode. Debug mode runs with extra assertions and overhead that will skew every reading you take.
flutter run --profile
The Flutter Inspector gives you a live, interactive view of your widget tree. Think of it as browser DevTools, but for your UI hierarchy. Here are the four toggles you'll use most.
Slow Animations
Complex animations can be impossible to debug at full speed. Toggling Slow Animations in the Inspector, or setting this in code, drops them to 20% speed so you can catch glitches frame by frame.
import 'package:flutter/scheduler.dart';
void main() {
timeDilation = 5.0; // 5× slower
runApp(const MyApp());
}
Guidelines & Baselines
Enables visual overlays that draw:
- Scroll direction indicators on scrollable widgets
- Widget boundaries (render object outlines)
- Text baselines to catch misaligned typography
This is particularly useful when you suspect a layout issue but cannot pinpoint which widget is the culprit.
Highlight Repaints
Every time a widget repaints, it flashes a continuous stream of color. If your entire screen is flashing because a small loading spinner is animating, you have a problem.
I once traced a scrolling jank issue to a blinking typing indicator that was forcing an entire chat screen to repaint every frame.
The fix is a RepaintBoundary. It tells Flutter to isolate that subtree into its own render layer, so its repaints do not cascade upward.
RepaintBoundary(
child: MyAnimatedWidget(),
)
Use this around widgets that animate independently, such as loaders, progress bars, and live counters.
Highlight Oversized Images
If an image is decoded at 2000×2000px but displayed at 100×100px, you're wasting memory on 39,900 pixels that will never be seen. This toggle shows them as negative and rotates them as shown in the image
The fix is straightforward. Constrain decoding at the source:
Image.network(
'https://example.com/large-image.jpg',
cacheWidth: 200,
cacheHeight: 200,
)
Flutter will downsample the image before caching it, significantly reducing memory pressure on image-heavy screens.
2. App Size Analysis — Know What You're Shipping
A bloated app takes longer to download, longer to install, and gives a worse first impression before the user has even launched it. The --analyze-size flag gives you a breakdown of exactly what's eating into your bundle.
For Android
flutter build apk --analyze-size
For iOS
flutter build ipa --analyze-size
This outputs a JSON file that you import directly into DevTools. Once loaded, you get two views:
Dominator Tree
Shows which packages are responsible for the largest chunks of your app size. If a utility package you barely use is claiming 3MB, it is worth reconsidering.
Call Graph
Maps how your dependencies import each other. This is how you find transitive dependencies you did not know you had. A package pulls another package, which pulls another, each silently adding weight.
Size is a feature.

The target is 60 frames per second. That gives each frame exactly 16.6 milliseconds to build and render. Any frame that exceeds this budget is called a jank, and users feel every one of them, even if they cannot name what's wrong.
The Performance tab lets you:
- Record a session of real app interactions
- View a flame chart of frame build times
- Click into expensive frames to see exactly which build, layout, or paint call consumed the most time
The key is to record specific interactions rather than letting it run indefinitely. Navigate to the screen you suspect is slow, hit record, reproduce the issue, then stop. A focused recording is far easier to read than five minutes of noise.
Look for the red frames in the timeline. Those are your jank frames. Drill into them, and you'll usually find one of three things:
- An expensive
build() method
- An unnecessary full-tree rebuild
- A synchronous operation blocking the main thread
Heavy JSON parsing, large computations, or image processing should not run on the UI isolate. Move them to another isolate using compute() whenever possible.
final result = await compute(parseLargeJson, jsonString);

4. Network & CPU Profiling — Under the Hood
Network Tab
If you've ever tried to debug an API issue by reading through a wall of flutter: {...} in the debug console, the Network tab will feel like a revelation.
It captures every HTTP request your app makes and displays:
- Full request and response headers
- Response body and status codes
- Request timing and duration
No more print-statement archaeology. You can see at a glance if a request is failing, slow, or returning unexpected data.
CPU Profiler
The CPU Profiler records a flame chart of method calls while your app runs. When something feels sluggish but the Performance tab does not show obvious jank, this is where you look.
It is especially good for catching:
- Expensive operations accidentally running on the main isolate
- Framework callbacks firing more often than expected
- Third-party packages with surprisingly heavy footprints
5. Memory & Debugger — Stability Under Pressure
Memory Tab
Memory leaks are silent killers. Your app might run perfectly in testing and degrade over time in production as memory accumulates and is never released.
The most common culprit in Flutter is forgetting to dispose() objects that hold onto resources.
class MyWidget extends StatefulWidget { ... }
class _MyWidgetState extends State<MyWidget>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 1),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
The Memory tab shows a real-time chart of heap allocation. If memory climbs steadily as you navigate through your app and never comes back down, you likely have a leak.
Reproduce the navigation repeatedly and watch the graph. A healthy app's memory should stabilize over time.

Debugger
The built-in debugger supports:
- Breakpoints
- Step-through execution
- Variable inspection
All without leaving DevTools.
This is useful for tracking down logic bugs that only appear in certain states, or for understanding a complex call chain without littering your codebase with print() calls.
Key Principles to Take Away
Profile Mode Is Non-Negotiable
Debug mode adds overhead that makes everything look slower than it is. Measure performance in profile mode, always.
Do Not Optimize Blindly
Reach for DevTools when you observe a real problem:
- A user complaint
- A visible stutter
- A reported crash
Premature optimization creates complexity without measurable benefit.
Older Devices Are Your Benchmark
An app that runs smoothly on a flagship device might choke on a three-year-old mid-range phone. Test on the weakest hardware your users are likely to have.
Dart DevTools removes the guesswork from Flutter performance.
Each panel answers a specific category of question:
- What's rendering
- What's too big
- What's too slow
- What's leaking
Use them together, and you'll spend less time wondering why your app feels sluggish and more time fixing it.
Thanks to Aswin Gopinathan for this wonderful workshop, from which I got the knowledge for this blog. Do check it out on Youtube