Flutter DevTools
Introduction to Flutter DevTools
Flutter DevTools is a suite of powerful debugging and performance profiling tools designed specifically for Flutter and Dart applications. It runs in your web browser and connects to your running Flutter app, giving you deep insight into the widget tree, performance, memory usage, network requests, and more.
DevTools is an essential part of every Flutter developer’s toolkit. Whether you are debugging a layout issue, tracking down a memory leak, or optimizing app performance, DevTools provides the visualization and data you need.
Opening DevTools
There are several ways to launch Flutter DevTools depending on your development environment:
From the Terminal
Opening DevTools via Terminal
# When running your app with flutter run, press v
flutter run
# App starts...
# Press: v (opens DevTools in your browser)
# Or launch DevTools directly
dart devtools
# Or open DevTools and connect to a specific URL
dart devtools --connect http://127.0.0.1:9100
From VS Code
With the Flutter extension installed, you can open DevTools directly from the editor:
- Open the Command Palette (
Ctrl+Shift+PorCmd+Shift+P) - Type “Flutter: Open DevTools” and select it
- Or click the DevTools icon in the status bar while debugging
From Android Studio
Android Studio has DevTools built into the IDE:
- Run your Flutter app in debug mode
- Go to View > Tool Windows > Flutter Inspector
- Or click the “Open DevTools” button in the Flutter Inspector panel
The Widget Inspector
The Widget Inspector is arguably the most frequently used tool in DevTools. It allows you to explore the widget tree, inspect widget properties, and understand layout behavior.
Widget Tree View
The widget tree shows every widget in your application in a hierarchical view. You can expand and collapse nodes to navigate through the tree structure.
Example Widget Tree in Inspector
MaterialApp
├── Scaffold
│ ├── AppBar
│ │ └── Text ('My App')
│ ├── Center
│ │ └── Column
│ │ ├── Text ('Hello')
│ │ ├── SizedBox (height: 16)
│ │ └── ElevatedButton
│ │ └── Text ('Click Me')
│ └── FloatingActionButton
│ └── Icon (Icons.add)
When you select a widget in the tree, the right panel shows its properties including:
- Widget properties: Parameters passed to the constructor
- Render object properties: Size, position, constraints, and painting details
- Layout information: Flex factors, alignment, padding, and margins
Selecting Widgets on Device
One of the most useful features is the ability to select a widget directly on your device or emulator and have DevTools highlight it in the widget tree.
Select Widget Mode
# Enable select widget mode:
# 1. In DevTools, click the "Select Widget Mode" button
# (cursor with crosshair icon in the top toolbar)
# 2. Tap any widget on your device/emulator
# 3. DevTools jumps to that widget in the tree
# 4. The widget is highlighted with a blue overlay on the device
# From terminal while running:
# Press: s (toggles select widget mode)
Layout Explorer
The Layout Explorer is a visual tool within the Widget Inspector that helps you understand Flex layouts (Row, Column, Flex). It shows a diagram of how flex children are arranged and their flex properties.
Debugging a Row Layout
// If this layout is not working as expected...
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(flex: 2, child: Text('Left')),
Expanded(flex: 1, child: Text('Right')),
],
)
// Select the Row in Widget Inspector
// The Layout Explorer shows:
// - Main axis direction and alignment
// - Cross axis alignment
// - Each child’s flex factor and actual size
// - Available vs used space
The Layout Explorer displays:
- Main axis direction (horizontal for Row, vertical for Column)
- Main axis alignment visualization
- Cross axis alignment visualization
- Each child’s flex factor and computed size
- Free space distribution
Performance View
The Performance view helps you identify and fix performance issues in your Flutter app. It shows frame rendering times, helps detect jank (dropped frames), and provides timeline traces.
Understanding Frame Times
Performance Targets
# Flutter targets 60 FPS (frames per second)
# Each frame budget: 16.67ms (1000ms / 60)
# Frame breakdown:
# - Build phase: Constructing the widget tree
# - Layout phase: Computing sizes and positions
# - Paint phase: Drawing pixels to the screen
# - Compositing: Assembling layers for the GPU
# Green bar = Frame rendered within budget (< 16.67ms)
# Red bar = Frame exceeded budget (jank/dropped frame)
# Blue bar = UI thread time
# Green bar = Raster thread time
Identifying Jank
Jank occurs when a frame takes longer than 16.67ms to render. The Performance view highlights these frames in red so you can identify and investigate them.
Common Causes of Jank
// BAD: Building expensive widgets every frame
@override
Widget build(BuildContext context) {
// This sorts a large list on every rebuild
final sorted = myLargeList.toList()..sort();
return ListView.builder(
itemCount: sorted.length,
itemBuilder: (ctx, i) => Text(sorted[i]),
);
}
// GOOD: Cache the sorted list and only recalculate when needed
List<String>? _cachedSorted;
@override
Widget build(BuildContext context) {
_cachedSorted ??= myLargeList.toList()..sort();
return ListView.builder(
itemCount: _cachedSorted!.length,
itemBuilder: (ctx, i) => Text(_cachedSorted![i]),
);
}
flutter run --profile), not debug mode. Debug mode includes extra checks and assertions that significantly slow down rendering. Performance measurements in debug mode are not representative of real-world performance.Memory View
The Memory view helps you understand and optimize your app’s memory usage. It shows heap allocation, garbage collection events, and allows you to take memory snapshots.
Key Memory Concepts
Memory View Features
# Memory view displays:
# 1. Memory usage chart over time
# - Heap usage (blue area)
# - External memory (green area)
# - RSS (Resident Set Size - total memory)
# 2. Allocation tracking
# - Which classes are being allocated
# - How many instances of each class
# - Total bytes consumed
# 3. Garbage Collection (GC) events
# - When GC runs (vertical lines on the chart)
# - How much memory was freed
# 4. Memory snapshots
# - Take a snapshot to see all live objects
# - Compare snapshots to find memory leaks
Detecting Memory Leaks
A memory leak occurs when objects are kept in memory even though they are no longer needed. Common causes in Flutter include:
- Not disposing controllers (
TextEditingController,AnimationController,ScrollController) - Not cancelling stream subscriptions
- Holding references to disposed widgets
- Circular references preventing garbage collection
Proper Resource Cleanup
class _MyPageState extends State<MyPage> {
late TextEditingController _textController;
late ScrollController _scrollController;
@override
void initState() {
super.initState();
_textController = TextEditingController();
_scrollController = ScrollController();
}
@override
void dispose() {
// Always dispose controllers to prevent memory leaks
_textController.dispose();
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
controller: _scrollController,
children: [
TextField(controller: _textController),
],
),
);
}
}
Network View
The Network view shows all HTTP requests made by your application. This is invaluable for debugging API calls, checking response times, and verifying request/response data.
Network View Information
# For each network request, you can see:
# - URL and HTTP method (GET, POST, PUT, DELETE)
# - Status code (200, 404, 500, etc.)
# - Request headers and body
# - Response headers and body
# - Timing (DNS lookup, connection, TLS, response time)
# - Request size and response size
# Example: Debugging a failing API call
# 1. Open the Network view in DevTools
# 2. Trigger the API call in your app
# 3. Find the request in the list
# 4. Check the status code and response body
# 5. Verify request headers (Authorization, Content-Type)
Logging View
The Logging view displays log messages from your application, including framework events, garbage collection events, and your custom log messages.
Adding Logging to Your App
import 'dart:developer' as developer;
// Simple logging
developer.log('User tapped the button');
// Logging with a name (category)
developer.log(
'API Response: 200 OK',
name: 'NetworkService',
);
// Logging with error details
developer.log(
'Failed to fetch data',
name: 'NetworkService',
error: 'Connection timeout after 30s',
level: 1000, // Severity level
);
// Debug print (also appears in logging view)
debugPrint('Widget rebuilt at \${DateTime.now()}');
developer.log() instead of print() for production-quality logging. The developer.log() function supports structured logging with names, levels, and error objects, and it integrates directly with DevTools.Debugging Layout Issues
Layout issues are among the most common problems Flutter developers face. DevTools provides several tools to diagnose them:
Debug Paint
Enabling Debug Paint
# From terminal while running the app:
# Press: p (toggles debug paint)
# Or programmatically:
import 'package:flutter/rendering.dart';
debugPaintSizeEnabled = true;
# Debug paint shows:
# - Blue lines: widget boundaries
# - Green arrows: padding
# - Dark blue areas: alignment space
# - Yellow arrows: margins
Common Layout Errors
Overflow Error Debugging
// ERROR: A RenderFlex overflowed by 42 pixels on the right
// This happens when children exceed the parent’s available space
// BAD: Row children too wide
Row(
children: [
Text('This is a very long text that overflows the row widget'),
Icon(Icons.arrow_forward),
],
)
// FIX: Wrap the text in Expanded or Flexible
Row(
children: [
Expanded(
child: Text(
'This is a very long text that now wraps properly',
overflow: TextOverflow.ellipsis,
),
),
Icon(Icons.arrow_forward),
],
)
Unbounded Height Error Debugging
// ERROR: Vertical viewport was given unbounded height
// This happens when a scrollable widget has no height constraint
// BAD: ListView inside Column without constraints
Column(
children: [
Text('Header'),
ListView( // Error! ListView needs bounded height
children: [Text('Item 1'), Text('Item 2')],
),
],
)
// FIX: Wrap ListView in Expanded
Column(
children: [
Text('Header'),
Expanded(
child: ListView(
children: [Text('Item 1'), Text('Item 2')],
),
),
],
)
Inspecting Widget Properties
When you select a widget in the Widget Inspector, the details panel shows all its properties. This is extremely helpful for understanding why a widget looks or behaves a certain way.
Property Inspection Example
// Select a Container in the Widget Inspector to see:
Container(
width: 200,
height: 100,
margin: EdgeInsets.all(16),
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(8),
boxShadow: [BoxShadow(blurRadius: 4, color: Colors.grey)],
),
child: Text('Hello'),
)
// DevTools shows:
// - constraints: BoxConstraints(w=200, h=100)
// - size: Size(200, 100)
// - margin: EdgeInsets(16, 16, 16, 16)
// - padding: EdgeInsets(24, 12, 24, 12)
// - decoration details (color, border radius, shadows)
Practical Debugging Session
Let’s walk through a complete debugging session using DevTools to fix a real layout issue.
Step 1: The Broken Layout
class ProfileCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Card(
child: Row(
children: [
CircleAvatar(radius: 30, child: Icon(Icons.person)),
Column(
children: [
Text('John Doe', style: TextStyle(fontSize: 18)),
Text('john.doe@example.com'),
Text('Senior Flutter Developer at TechCorp International'),
],
),
Icon(Icons.chevron_right),
],
),
);
}
}
Step 2: Use DevTools to Diagnose
# 1. Open DevTools Widget Inspector
# 2. Select the Row widget
# 3. Layout Explorer shows: children exceed available width
# 4. The Column’s text widgets have unconstrained width
# 5. Solution: Wrap Column in Expanded
Step 3: The Fixed Layout
class ProfileCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: EdgeInsets.all(12),
child: Row(
children: [
CircleAvatar(radius: 30, child: Icon(Icons.person)),
SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('John Doe', style: TextStyle(fontSize: 18)),
Text('john.doe@example.com'),
Text(
'Senior Flutter Developer at TechCorp International',
overflow: TextOverflow.ellipsis,
),
],
),
),
Icon(Icons.chevron_right),
],
),
),
);
}
}
Summary
In this lesson you learned:
- How to open Flutter DevTools from the terminal, VS Code, and Android Studio
- Using the Widget Inspector to explore the widget tree and inspect properties
- The Layout Explorer for debugging Flex layouts (Row, Column)
- Selecting widgets directly on the device to find them in the tree
- The Performance view for identifying frame drops and jank
- The Memory view for detecting memory leaks and tracking allocations
- The Network view for debugging HTTP requests and API calls
- The Logging view for structured application logs
- Practical techniques for debugging common layout issues
Practice Exercise
Build a simple app with a ListView inside a Column (which will cause a layout error). Use Flutter DevTools to: (1) identify the error in the Widget Inspector, (2) use the Layout Explorer to understand the constraint issue, (3) fix the layout by wrapping the ListView in an Expanded widget, (4) take a memory snapshot before and after scrolling the list. Also practice using select widget mode to identify specific widgets on screen.