Skip to content

check_for_equals_in_render_object_setters

v1.0.0WarningConfigurableWidget Best Practices

Flags a RenderObject setter that calls a mark-dirty method without comparing the incoming value first.

updateRenderObject runs on every rebuild and assigns every property, whether or not it differs. An unguarded setter therefore turns each rebuild into a full relayout or repaint of that subtree, even when nothing about it changed. At best that is wasted frames on a hot path; when the layout pass itself causes another rebuild, the two feed each other and the app stops rendering.

Flutter’s own render objects all use the same shape: compare, then assign and mark dirty.

This rule is in the opinionated preset, so it is on with preset: opinionated and preset: pedantic.

See also: Flutter: RenderObject, RenderObjectWidget.updateRenderObject

class RenderBadge extends RenderBox {
RenderBadge(this._color);
Color _color;
set color(Color value) {
_color = value;
markNeedsPaint(); // repaints on every rebuild, unchanged or not
}
}

The early return is the idiomatic form:

class RenderBadge extends RenderBox {
RenderBadge(this._color);
Color _color;
set color(Color value) {
if (_color == value) return;
_color = value;
markNeedsPaint();
}
}

The wrapping form works just as well:

set color(Color value) {
if (_color != value) {
_color = value;
markNeedsPaint();
}
}

A property that changes the object’s size needs markNeedsLayout; one that only changes how it draws needs markNeedsPaint. Both need the same guard:

class RenderBadge extends RenderBox {
RenderBadge(this._padding, this._color);
double _padding;
Color _color;
set padding(double value) {
if (_padding == value) return;
_padding = value;
markNeedsLayout(); // size changed
}
set color(Color value) {
if (_color == value) return;
_color = value;
markNeedsPaint(); // only the painting changed
}
}

The recognised methods are markNeedsLayout, markNeedsPaint, markNeedsCompositingBitsUpdate, markNeedsSemanticsUpdate and markNeedsLayoutForSizedByParentChange.

If your render objects mark themselves dirty through a helper of your own, name it so the rule keeps working:

analysis_options.yaml
many_lints:
rules:
check_for_equals_in_render_object_setters:
additional_methods: [markNeedsCustomPass]
// Reported under the config above
set weight(double value) {
_weight = value;
markNeedsCustomPass();
}

Guard detection is deliberately loose. Any ==, != or identical call anywhere in the setter body counts as a guard, even one comparing something else entirely. A false positive on a setter that is in fact guarded would be far more annoying than missing an exotic shape.

Only setters that mark dirty are considered. A setter that merely assigns has nothing to guard against, and is never reported.

Only a block body is examined. An expression-bodied setter is skipped.

No quick fix. Which field to compare — and whether an unchanged value should really skip the work — is a decision about the render object.

Option Type Default Description
additional_methods list of strings [] Extra methods treated as marking the render object dirty
many_lints.yaml
rules:
check_for_equals_in_render_object_setters: false

To keep the rule on but skip certain paths, use per-rule exclude.