---
title: 'Developer Tools'
source: 'https://academia.sh/en/courses/flutter/developer-tools'
course: Flutter
language: en
updated: '2026-10-21T05:02:53+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Distinguishes what the widget inspector, layout inspection, and the performance view each show'
    - 'Matches the constraint and width values in the inspector with the measured record'
    - 'Reads from the constraint the reason a widget looks unexpectedly wide'
    - 'Explains that hot reload rebuilds the tree and preserves the state'
---

# Developer Tools

The widget inspector, layout inspection, and the performance view show a tree while it runs. The lesson matches the constraint and width the inspector shows with the measured record.

In the previous lesson a test failed, and the path where the error occurred was named. Seeing why it happens is the subject of a different task: opening the tree while it runs and looking at it. This lesson reads the development tools of the framework along the sizing axis. When a tool shows the width of a widget on the screen, it is in fact answering the question of the course: who determined this width.

## Four Tools

**The widget inspector** shows the widget tree of the running app. When a widget is selected, you see not only its properties but also the constraint it received and the resulting size. The documentation of the framework says this on the subject: "The constraints flow down the tree, and the sizes flow back up." That is, constraints go down the tree and sizes come back up; the two fields to look at in layout problems are `size` and `constraints`.

**Layout inspection** is a separate view inside the inspector for flexible widgets. When a widget related to a row, a column, or flexible children is selected, the flex factor, the flex fit, and the constraints are visualized; constraint violations are shown in red, and overflow errors are shown with a pattern of yellow stripes. The values you change in this view are not written to the source code and are reverted on hot reload.

**The performance view** shows time, not width: it lines frames up on a chart, marks slow frames in red, and opens what work was done in a frame on the event chart. There is also an option that shows at what moment the widget structures were built. The documentation recommends taking the measurement in the performance measurement mode, not in development mode, because frame times in development mode do not reflect the performance of the release version. The numbers of this lesson contain no time; the performance view is the subject of the next lesson.

**Hot reload** sends the changed source code to the running app and rebuilds the tree. `main` and `initState` do not run again; the state of the app is preserved. According to the documentation, this works only in development mode. Because the state is preserved, a change in the source code may not be visible on the screen: for example, changing an initial value has no effect if the value has already been set. Then a hot restart, which starts the app from the beginning, is needed.

## The Measured Layer

Every lesson of the course uses the same plain Dart model. The model gives a widget tree the screen width and records who determines each node's width with four names. The file below produces no output; the measured blocks import it.

```dart
// sizing.dart — classifies who decides a widget's width
const sizings = ['forced', 'chosen', 'clamped', 'stretched'];

class Limit {
  final int min;
  final int? max; // null: unbounded
  const Limit(this.min, this.max);

  bool get tight => max == min;
  Limit loosen() => Limit(0, max);
  Limit deflate(int by) => Limit(_floor(min - by), max == null ? null : _floor(max! - by));
  String bounds() => '$min..${max ?? 'inf'}';
}

int _floor(int value) => value < 0 ? 0 : value;

class Part {
  final String kind;
  final String name;
  final int? width; // null: as wide as it can get
  final List<Part> children;
  final int factor;
  final bool tightFlex;
  const Part(this.kind, this.name, this.width, this.children,
      {this.factor = 0, this.tightFlex = true});
}

Part leaf(String name, int width) => Part('leaf', name, width, const []);
Part fill(String name) => Part('leaf', name, null, const []);
Part pad(String name, int padding, Part child) => Part('pad', name, padding, [child]);
Part center(String name, Part child) => Part('center', name, null, [child]);
Part fixed(String name, int width, Part child) => Part('fixed', name, width, [child]);
Part column(String name, List<Part> children) => Part('column', name, null, children);
Part row(String name, List<Part> children) => Part('row', name, null, children);
Part flex(String name, int factor, Part child, {bool tight = true}) =>
    Part('flex', name, null, [child], factor: factor, tightFlex: tight);

class Sizing {
  final String sizing;
  final String path;
  final int width;
  final Limit limit;
  const Sizing(this.sizing, this.path, this.width, this.limit);
}

Sizing classify(String path, int? desire, Limit limit) {
  if (limit.tight) return Sizing('forced', path, limit.min, limit);
  if (desire == null) {
    return limit.max == null
        ? Sizing('clamped', path, limit.min, limit)
        : Sizing('stretched', path, limit.max!, limit);
  }
  if (desire < limit.min) return Sizing('clamped', path, limit.min, limit);
  if (limit.max != null && desire > limit.max!) {
    return Sizing('clamped', path, limit.max!, limit);
  }
  return Sizing('chosen', path, desire, limit);
}

int layout(Part part, Limit limit, List<Sizing> log, [String path = '']) {
  final here = '$path/${part.name}';
  final below = <Sizing>[];
  if (part.kind == 'fixed') {
    final own = classify(here, part.width, limit);
    layout(part.children.first, Limit(own.width, own.width), below, here);
    log..add(own)..addAll(below);
    return own.width;
  }
  int? desire;
  switch (part.kind) {
    case 'leaf':
      desire = part.width;
    case 'pad':
      final space = 2 * part.width!;
      desire = layout(part.children.first, limit.deflate(space), below, here) + space;
    case 'center':
      final inner = layout(part.children.first, limit.loosen(), below, here);
      desire = limit.max == null ? inner : null;
    case 'flex':
      desire = layout(part.children.first, limit, below, here);
    case 'column':
      var widest = 0;
      for (final child in part.children) {
        final width = layout(child, limit.loosen(), below, here);
        if (width > widest) widest = width;
      }
      desire = widest;
    case 'row':
      final logs = [for (final _ in part.children) <Sizing>[]];
      final bounded = limit.max != null;
      var used = 0;
      var factors = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind == 'flex' && bounded) {
          factors += child.factor;
        } else {
          used += layout(child, const Limit(0, null), logs[i], here);
        }
      }
      final left = bounded ? _floor(limit.max! - used) : 0;
      var given = 0;
      var seen = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind != 'flex' || !bounded) continue;
        seen += child.factor;
        final share = (seen == factors ? left : left * seen ~/ factors) - given;
        given += share;
        final room = child.tightFlex ? Limit(share, share) : Limit(0, share);
        used += layout(child, room, logs[i], here);
      }
      for (final entries in logs) {
        below.addAll(entries);
      }
      desire = bounded ? null : used;
  }
  final own = classify(here, desire, limit);
  log..add(own)..addAll(below);
  return own.width;
}

List<Sizing> measure(Part root, int screen) {
  final log = <Sizing>[];
  layout(root, Limit(screen, screen), log);
  return log;
}

void printSizes(List<Sizing> log) {
  for (final entry in log) {
    print('${entry.sizing}: ${entry.path} width=${entry.width} constraint=${entry.limit.bounds()}');
  }
}

void countSizings(List<Sizing> log) {
  for (final name in sizings) {
    print('$name: ${log.where((entry) => entry.sizing == name).length}');
  }
  print('parent: ${log.where((entry) => entry.sizing != 'chosen').length}');
}
```

In this lesson the model builds this sizing's rule; the model is not the framework itself. In the model, the two values the inspector shows for a widget are the `width` and `constraint` fields in the `printSizes` output. The model does not carry the performance view or hot reload; the second is approached by measuring the tree again.

## The Inspected Screen

The tree of the screen is a column: a label that requests 150 units and a `Chip` that requests 80 units inside a `Center`. The tree is built by a function that takes the width of the chip.

```dart
// panel_model.dart — the widget tree of the inspected screen
import 'sizing.dart';

Part buildPanel(int chipWidth) => pad('Screen', 16, column('Body', [
      leaf('Label', 150),
      center('Center', leaf('Chip', chipWidth)),
    ]));
```

In the source counterpart of this tree, `Center` only wraps the chip. The developer knows that the chip is 80 units and expects the space given to it to be about that much as well. This expectation lasts until the inspector is consulted.

## A Widget That Looks Unexpectedly Wide

The screen is 360 units wide. The block below prints the whole record; each line holds the values the inspector would show for a widget.

```dart
// main.dart
import 'sizing.dart';
import 'panel_model.dart';

void main() {
  final log = measure(buildPanel(80), 360);
  printSizes(log);
}
```

```text
forced: /Screen width=360 constraint=360..360
forced: /Screen/Body width=328 constraint=328..328
chosen: /Screen/Body/Label width=150 constraint=0..328
stretched: /Screen/Body/Center width=328 constraint=0..328
chosen: /Screen/Body/Center/Chip width=80 constraint=0..328
```

`Chip` requests 80 units and receives 80 units: **chosen**. `Center`, on the other hand, is 328 units, much wider than the chip. Under a bounded constraint, a centering widget does not report a request; it spreads to the widest value given to it. In the language of the course, this is **stretched** sizing. When `Center` is selected in the inspector, the `size` you see is 328 and the `constraints` are between 0 and 328. The developer looked at the chip and expected `Center` to be narrow too; but its width was determined not by the chip but by the constraint of its parent.

## The Constraint Reaches the Sibling Unchanged

In M11/K05 the question was what an operation leaves for the resource: was the resource consumed once, and what is its state afterward. For a layout constraint this question is invalid, because a layout constraint is not consumed. The column loosens the constraint that comes to the body and gives the same loose constraint to every child; when the label takes its width, the share of `Center` does not shrink. The block below lists all widgets that receive the constraint `0..328`.

```dart
// main.dart
import 'sizing.dart';
import 'panel_model.dart';

void main() {
  final log = measure(buildPanel(80), 360);
  printSizes(log.where((entry) => entry.limit.bounds() == '0..328').toList());
}
```

```text
chosen: /Screen/Body/Label width=150 constraint=0..328
stretched: /Screen/Body/Center width=328 constraint=0..328
chosen: /Screen/Body/Center/Chip width=80 constraint=0..328
```

`Label` and `Center` receive the same constraint. `Chip` receives the same constraint too, because the centering widget loosens the constraint but keeps the upper bound. This is why the constraint fields of two siblings side by side in the inspector look the same. The only thing that narrows the constraint is the padding, and in this tree it is only at the `Screen` level.

## Overflow and Layout Inspection

What happens if the chip's request exceeds the constraint? The block below makes the chip 400 units and looks at the records under `Center`.

```dart
// main.dart
import 'sizing.dart';
import 'panel_model.dart';

void main() {
  final log = measure(buildPanel(400), 360);
  printSizes(log.where((entry) => entry.path.startsWith('/Screen/Body/Center')).toList());
}
```

```text
stretched: /Screen/Body/Center width=328 constraint=0..328
clamped: /Screen/Body/Center/Chip width=328 constraint=0..328
```

The chip requests 400, and the most that is allowed is 328. Its width is pulled to 328 and it is **clamped**. In the real framework the same situation appears as an overflow error: the striped yellow pattern of layout inspection is useful exactly here. In the model, overflow is not an image but a class; the record says only that the request falls outside the range.

## Hot Reload Measures the Tree Again

Hot reload rebuilds the tree. In the model, its counterpart is calling the same tree function with the new state and measuring the tree again. The block below changes the chip width from 80 to 400 and prints the counts of the two measurements side by side.

```dart
// main.dart
import 'sizing.dart';
import 'panel_model.dart';

void main() {
  for (final chip in [80, 400]) {
    print('chip=$chip');
    countSizings(measure(buildPanel(chip), 360));
  }
}
```

```text
chip=80
forced: 2
chosen: 2
clamped: 0
stretched: 1
parent: 3
chip=400
forced: 2
chosen: 1
clamped: 1
stretched: 1
parent: 4
```

When the chip value changes, one widget moves from **chosen** to **clamped**; the parent count rises from three to four. In real hot reload, the state of the app is preserved; in the model, measuring again builds a tree from scratch each time. The model has no preserved state.

## Reading What the Tool Shows

The order to follow when looking at an inspector is simple. First select the widget. Then look at the constraint it received: if it is tight, the parent has imposed the size. If it is bounded and loose, look at the widget's own request: if the request fits the range, it is **chosen**; if it does not fit, it is **clamped**. If there is no request and the upper bound is finite, the width is the upper bound: **stretched**. These three questions separate all four sizings.

The two snippets below are the framework code counterpart of the same question. The first turns on a flag that draws the bounds of every widget on the screen; the second shows a centered wrapper.

```dart
// Platform code: drawing layout bounds
void main() {
  debugPaintSizeEnabled = true;
  runApp(const ExampleScreen());
}
```

This flag draws the bounds of every widget and is effective only in development mode.

```dart
// Platform code: centered wrapper
Column(
  children: [
    const Text('Label'),
    Center(child: Chip(label: Text('Label'))),
  ],
)
```

In this two-line layout, `Center` spreads to the width given to it. The inspector shows this spreading; in the model, the same spreading has been measured for the `Center` node.

## Common Misconception

**The content of a widget that looks wide in the inspector is wide too.** This is not so. `Center` is 328 units, but the chip inside it is 80 units. The bounds of a widget and its content are separate things; the inspector shows the bounds, and the content's own request must be read separately.

## The Bounding Measurement

The last measurement shows that the rule, like the inspector, sees only the current constraint. When the screen goes from 360 units down to 240 units, the width of `Center` stays tied to the screen, not to the chip.

```dart
// main.dart
import 'sizing.dart';
import 'panel_model.dart';

void main() {
  for (final screen in [360, 240]) {
    final log = measure(buildPanel(80), screen);
    printSizes(log.where((entry) => entry.path == '/Screen/Body/Center').toList());
  }
}
```

```text
stretched: /Screen/Body/Center width=328 constraint=0..328
stretched: /Screen/Body/Center width=208 constraint=0..208
```

The focus node is `/Screen/Body/Center`. It comes out **stretched** on both screens; its width falls from 328 to 208, while the chip is 80 units in both measurements. This lesson's sizing: **stretched**. The screen determines the width of the centering widget.

**QR1.** The model builds this sizing's rule; the model is not the framework itself. In the model, the inspector's value is one record line. A real inspector shows not only the constraint and size of a widget but also the properties of the render object and the flex factor; these are not in the model. The frame chart of the performance view and the preserved state of hot reload are not measured in the model either.

## Summary

- This lesson's sizing: **stretched**. `Center` did not report a request under a bounded constraint and spread to 328 units; the chip inside it stayed **chosen** at 80 units.
- The widget inspector shows the constraint and size, layout inspection shows flexible widgets, and the performance view shows frames.
- The constraint and width the inspector shows are, in the model, the `constraint` and `width` fields of a `printSizes` line.
- A layout constraint is not consumed: the same loose constraint came down as `0..328` for `Label`, `Center`, and `Chip` alike.
- Hot reload rebuilds the tree and preserves the state; its counterpart in the model is measuring the tree again with the new state.

## Next Step

The inspector shows a problem, but most of the time the problem is not a wrong width but unnecessary work. The next lesson, `Performance Tuning`, covers the methods that reduce unnecessary rebuilds in the tree, and the relayout boundary, where the change of a widget does not leak upward.
