-
-
Notifications
You must be signed in to change notification settings - Fork 0
Extracted Features
analyze records these build-tree features for each rebuild scope: a State subclass, a
ConsumerWidget or HookConsumerWidget, or a supported state-management builder callback. The
analyzer emits 18 fields. Output Formats lists their JSON keys and wire types.
All build metrics below are computed by one traversal (TreeExtractor); identity fields are
assigned during scope discovery. The metric scopes are easiest to understand from these
traversal rules:
-
Root. Analysis starts at the scope's body: the
build()body of aStatesubclass or consumer widget, or the callback body of a builder-pattern widget. Scopes nest, and the metrics overlap deliberately — aBlocBuildercallback inside aState.build()is counted in theStaterow (a parent rebuild re-runs it) and gets a row of its own. -
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 ownbuild()body is resolved and analyzed too, breadth-first, transitively through grandchildren. A customStatefulWidgetchild has nobuild()of its own, so its state class'sbuild()is what reruns on rebuild, so the traversal resolves the State class (from thecreateState()body, falling back to the library'sextends State<Widget>declaration) and analyzes that instead. Each class is analyzed once per record, even if instantiated many times. -
Helper methods. A reference to a widget-returning callable inside the widget tree (nested in
widget arguments or in a
returnexpression) 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 thehelper*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 ofbuild(). Helper→helper chains are followed, and helpers of custom child widgets are analyzed as well (deduped per class + member). -
Const boundary. A
constwidget 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'sbuild()is not recursed into. -
Non-widget allocations. Instantiations of non-
Widgettypes (EdgeInsets,TextStyle,BorderRadius, application models, …) are not counted as widgets and add no widget nesting depth. Non-const instances incrementvalueObjectAllocCount; const instances do not. Their arguments are still searched for widgets nested inside them. - 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.
| Feature | Type | Description |
|---|---|---|
instanceId |
String |
Stable ID for the record: a 31-bit hash of "<root-relative file path>:<class name>" for a State scope, or "<root-relative file path>:<scopeType>:<scopeName>#<n>" for any other rebuild scope (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 rebuild scope, relative to the analyzed project root (the same path the ID hashes). |
scopeName |
String |
Class name of the scope (e.g. _MyWidgetState), or <Widget>_builder for a builder callback (e.g. BlocBuilder_builder). (Named stateClassName before rebuild-scope support.)
|
scopeType |
String |
Kind of rebuild scope: State, ConsumerWidget, or the builder widget name (BlocBuilder, BlocSelector, BlocConsumer, Consumer, Selector, Obx, GetX, GetBuilder, Observer). |
| 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. |
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. |
| 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.)
|
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(). |
boolfeatures are serialized as1/0in the JSONL. See Output Formats for the wire format.
Commands
Reference
Internals
Contributing