Skip to content

Extracted Features

albertoodev edited this page Jul 21, 2026 · 7 revisions

Extracted Features

Definitions of the build-tree features analyze records per State subclass. These are the 17 fields the analyzer actually emits; for the exact JSON keys and wire types, see Output Formats.


How the tree is traversed

All build metrics below are computed by one traversal (TreeExtractor); identity fields are assigned during State-class discovery. The metric scopes are easiest to understand from these traversal rules:

  1. Root. Analysis starts at the build() body of the State subclass.
  2. Custom child widgets. Every non-const instantiation of a custom widget class (any class outside the Dart SDK and package:flutter) is followed: that class's own build() body is resolved and analyzed too, breadth-first, transitively through grandchildren. A custom StatefulWidget child has no build() of its own, so its State class's build() is what reruns on rebuild, so the traversal resolves the State class (from the createState() body, falling back to the library's extends State<Widget> declaration) and analyzes that instead. Each class is analyzed once per record, even if instantiated many times.
  3. Helper methods. A reference to a widget-returning callable inside the widget tree (nested in widget arguments or in a return expression) is treated as a helper. Three reference forms count: a direct invocation (_buildRow()), a method tear-off (items.map(_buildRow), itemBuilder: _buildRow), and an explicit widget-returning getter (_header). Resolution is element-based, so instance methods, static methods of other classes, and top-level widget functions all resolve; plain widget fields (synthetic getters) are data, not helpers, and are excluded. Helper bodies feed the helper* widget features, and their execution cost signals (iterations, per-element widgets, allocations, list-rendering strategy, layout-dependent builders, and decision points) merge into the corresponding aggregate output features, because helpers run as part of build(). Helper→helper chains are followed, and helpers of custom child widgets are analyzed as well (deduped per class + member).
  4. Const boundary. A const widget instantiation is a canonicalized subtree that Flutter's reconciliation skips, so it carries no per-rebuild cost: it is counted once as a const unit, its children are not entered, and a const custom child's build() is not recursed into.
  5. Non-widget allocations. Instantiations of non-Widget types (EdgeInsets, TextStyle, BorderRadius, application models, …) are not counted as widgets and add no widget nesting depth. Non-const instances increment valueObjectAllocCount; const instances do not. Their arguments are still searched for widgets nested inside them.
  6. Broken files. A file with compile errors is skipped entirely (and reported in the summary): its unresolved types would silently classify every widget as a value object and emit a near-zero garbage row.

"Across the build tree" below always means: the root build() plus every custom child widget's build() reached by rule 2.


Identity

Feature Type Description
instanceId String Stable ID for the record: a 31-bit hash of "<root-relative file path>:<class name>" (forward slashes on every platform), rendered as hex. Relative to the analyzed project root, so the ID is stable across machines and checkout directories. Used to join static features with runtime profiler events emitted under the same ID. (Hashed from the absolute path before 2026-07-20; older JSONL files carry machine-specific IDs.)
filePath String Source file containing the State subclass, relative to the analyzed project root (the same path the ID hashes).
stateClassName String Name of the State subclass (e.g. _MyWidgetState).

Build tree: widget counts

Feature Type Description
treeNonConstWidgetCount int Total non-const widget instantiations across the build tree. Excludes const widgets (see treeConstWidgetCount), value objects, and widgets built inside helper bodies (those go to helperWidgetCount).
treeMaxWidgetNestingDepth int Deepest widget nesting level reached across the build tree. Depth composes across classes: a child widget instantiated at depth d whose own build reaches internal depth k yields an absolute depth of d + k (for a StatefulWidget child, k is measured in its State class's build). A const widget occupies its own level but its subtree adds nothing. Custom widgets instantiated inside helper bodies restart from depth 0 (helper nesting is tracked separately in helperMaxWidgetNestingDepth).
treeConstWidgetCount int Number of const widget instantiations across the build tree, as a raw count, not a ratio. Because of the const boundary rule, an entire const subtree counts as 1, and const widgets inside helper bodies are folded into helperWidgetCount instead. (Emitted as constConstructorRatio before 2026-07-18; older JSONL files use that key.)
rootBuildReturnsConstWidget bool (0/1) Whether the top-level return expression of the root build() is a const instantiation. Applies to both return const …; statements and expression bodies (Widget build(_) => const …;). Returns inside closures never set it, whether the closure sits in widget arguments or in the build preamble.

Build tree: list rendering and layout

Both features aggregate across the whole tree (root build, custom child builds, and helper bodies); a match anywhere counts.

Feature Type Description
treeListRenderingStrategy int (0/1/2) The most expensive list-shaped rendering form reachable from build(), ranked by UI-thread build cost and aggregated as the max. 0 = none: no list-shaped multi-child rendering, including a fixed-arity children: literal (an if-element is an O(1) branch, not a scaling child count). 1 = lazy / viewport-bounded: ListView/GridView .builder/.separated/.custom, or a SliverList/SliverGrid. 2 = eager / O(N): a ListView/GridView fed a concrete children: (every child builds on every rebuild, no viewport culling), or a Column/Row/Wrap/Flex whose children: is runtime-length. Runtime-length is decided by the shape of the expression, not an op whitelist: a spread or for-element inside the literal, or any non-literal expression such as a bare list variable, items.map(…).toList(), List.generate(…), or a helper call. (Replaces the boolean buildUsesListViewBuilder on 2026-07-20; the boolean missed every eager form, including SingleChildScrollView over a mapped Column.)
usesLayoutDependentBuilder bool (0/1) The tree instantiates LayoutBuilder, CustomMultiChildLayout, or Flow, widgets whose build depends on incoming layout constraints.

Build tree: complexity and iteration

Feature Type Description
treeCyclomaticComplexity int Cyclomatic complexity summed over every build body in the tree (root + custom children), plus the decision points of every analyzed helper body (each helper contributes its complexity − 1, so the shared +1 base is not re-added per helper). Each build body starts at 1 and gains +1 per if (statement or collection-if element), for (statement or collection-for element, for-in included), while, do, switch case (statement cases and switch-expression cases; statement default excluded), catch, &&, ||, ??, and ternary ?:. Because it is a sum, a tree of n build methods has a floor of n.
treeIterationCount int Number of iteration constructs across all build bodies and helper bodies: for / while / do statements, collection-for elements inside literals, and calls to linear collection ops (forEach, map, where, any, every, reduce, fold, expand, generate, sort, firstWhere, lastWhere, singleWhere). Each op in a chain like .where(…).map(…) counts individually.
treeMaxIterationNestingDepth int Maximum lexical nesting depth of those iteration constructs within any single analyzed body, covering build bodies and helper bodies. Chained collection ops count as sequential, not nested (only the callback argument runs per element). Nesting is not composed across classes: a child widget created inside a parent's loop does not nest the child's own loops.
helperReferenceCount int Number of reference sites of widget-returning helpers inside build bodies (root + custom children): direct invocations, method tear-offs, and explicit getter reads (rule 3 above). Reference sites, not distinct methods: calling _buildRow() three times counts 3. Not counted: references made from inside helper bodies, linear collection ops that happen to yield a Widget (e.g. firstWhere on a List<Widget>, which is an iteration, see treeIterationCount), any member named build (e.g. super.build(context) from the keep-alive mixin), plain widget fields (synthetic getters), and invocations of function-typed variables or closures.
iterationWidgetCount int Non-const widget instantiations inside a per-element scope, summed across build bodies and helper bodies: a loop body, a linear-collection-op callback (.map((e) => Card(...))), or the argument list of a lazy ListView/GridView constructor (itemBuilder runs per visible element). The per-element cost multiplier of a rebuild: distinguishes ten one-shot widgets from one widget built ten times. (Added 2026-07-20; motivated by loop-cost findings in performance/energy prediction literature.)
valueObjectAllocCount int Non-const, non-widget instance creations (EdgeInsets, TextStyle, BoxDecoration, …) across build and helper bodies. This is the allocation/GC pressure paid on every rebuild. const value objects are canonicalized and excluded, so const-ing a padding lowers this count. (Added 2026-07-20; motivated by allocation-density findings in energy prediction literature.)

Helper methods

Cover the bodies of widget-returning helpers reached from a build body (rule 3 above): helpers of the root State class and of every analyzed custom child widget (instance methods, getters, static methods, and top-level widget functions), followed transitively through helper→helper chains, each body analyzed once.

Feature Type Description
helperWidgetCount int Total widget instantiations inside all analyzed helper bodies. Unlike treeNonConstWidgetCount, const widgets are included here. Custom widgets instantiated in a helper still contribute their own build() tree to the tree-scoped metrics.
helperMaxWidgetNestingDepth int Maximum widget nesting depth reached inside any single helper body, measured from 0 at the helper body's root. Not composed with the depth of the call site in build().

bool features are serialized as 1/0 in the JSONL. See Output Formats for the wire format.

Clone this wiki locally