Skip to content

prefer_single_setstate

v0.4.0WarningFixConfigurableCode Quality

Flags a method in a State subclass that calls setState() more than once. Each call schedules a rebuild, so calling it twice for one logical change rebuilds twice and briefly exposes an intermediate state.

This rule is in the opinionated preset.

See also: State.setState()

class CartView extends StatefulWidget {
const CartView({super.key});
@override
State<CartView> createState() => _CartViewState();
}
class _CartViewState extends State<CartView> {
int _itemCount = 0;
String _status = 'empty';
void _addItem() {
setState(() {
_itemCount += 1;
});
setState(() {
_status = 'has items';
});
}
@override
Widget build(BuildContext context) => Text('$_itemCount $_status');
}
class CartView extends StatefulWidget {
const CartView({super.key});
@override
State<CartView> createState() => _CartViewState();
}
class _CartViewState extends State<CartView> {
int _itemCount = 0;
String _status = 'empty';
void _addItem() {
setState(() {
_itemCount += 1;
_status = 'has items';
});
}
@override
Widget build(BuildContext context) => Text('$_itemCount $_status');
}

Two calls in opposite if branches never both run, but they are still counted — the rule reads the whole method, not the paths through it. Hoist the shared setState around the branch:

// Reported
void _toggleBad(bool on) {
if (on) {
setState(() => _status = 'on');
} else {
setState(() => _status = 'off');
}
}
// Not reported
void _toggleGood(bool on) {
setState(() => _status = on ? 'on' : 'off');
}

Projects with a state abstraction that does not extend Flutter’s State can opt that base class in:

analysis_options.yaml
many_lints:
rules:
prefer_single_setstate:
state_base_classes: [AppState]

A closure or nested function stops the search. Calls inside one run later and belong to their own scope, so they are neither counted nor merged:

// Not reported: the second call is inside a callback.
void _load() {
setState(() => _status = 'loading');
_future.then((_) {
setState(() => _status = 'done');
});
}

The same goes for two calls in different methods — each method is counted on its own.

In an async method, two calls around an await are still reported, even though they genuinely happen in different frames and merging them would change behaviour. Split the work into two methods, or suppress the diagnostic on that line.

Only classes reachable from State (or a class named in state_base_classes) are examined. A plain controller calling a method it happens to have named setState is left alone.

This rule is in the opinionated preset, so it is on with preset: opinionated, or by name:

many_lints.yaml
rules:
prefer_single_setstate: true

To turn it off again:

many_lints.yaml
rules:
prefer_single_setstate: false

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

Option Type Default Description
state_base_classes list of strings [] Additional non-State base classes whose subclasses should be treated as state classes