Flutter Setup & First App

Flutter DevTools

50 min Lesson 7 of 12

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.

Note: Flutter DevTools comes pre-installed with the Flutter SDK. You do not need to install any additional packages to use it. It is available for all platforms: Android, iOS, web, and desktop.

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+P or Cmd+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
Tip: DevTools opens in your default web browser. For the best experience, use Google Chrome, as DevTools is optimized for Chromium-based browsers.

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)
Tip: Select widget mode is incredibly useful when you cannot figure out which widget is causing a particular visual element. Just tap on it and DevTools will show you exactly which widget it is and where it sits in the tree.

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]),
  );
}
Warning: Always profile in profile mode (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()}');
Note: Use 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),
          ],
        ),
      ),
    );
  }
}
Tip: Make DevTools part of your regular development workflow. Open it alongside your editor and keep the Widget Inspector visible. This habit will save you countless hours of debugging layout issues.

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.