Skip to content

proper_super_calls

v0.4.0WarningFixConfigurableControl Flow

Warns when super lifecycle methods are called in the wrong order in State subclasses. Methods like initState, didUpdateWidget, activate, didChangeDependencies, and reassemble must call super first. Methods like deactivate and dispose must call super last.

Flutter’s State lifecycle splits into two halves, and super goes on opposite ends of each:

  • super first — initState, didChangeDependencies, didUpdateWidget, activate, reassemble. The framework sets up its own state before yours reads it.
  • super last — deactivate, dispose. Your cleanup runs while the framework’s state is still there to clean up against.

Getting it backwards is not always visible immediately: super.dispose() first tears down the element, and a controller disposed after it may throw only under a particular navigation. The quick fix moves the call to the right end.

See also: State.initState | State.dispose

super.initState() after your own setup:

class _EditorState extends State<Editor> {
late final TextEditingController _controller;
@override
void initState() {
_controller = TextEditingController(text: widget.initialText);
super.initState();
}
@override
Widget build(BuildContext context) => const SizedBox();
}

super.dispose() before your cleanup:

class _EditorState extends State<Editor> {
late final TextEditingController _controller;
@override
void dispose() {
super.dispose();
_controller.dispose();
}
@override
Widget build(BuildContext context) => const SizedBox();
}

deactivate follows dispose — super goes last there too:

class _EditorState extends State<Editor> {
@override
void deactivate() {
super.deactivate();
_unsubscribeFromDocument();
}
@override
Widget build(BuildContext context) => const SizedBox();
}
class _EditorState extends State<Editor> {
late final TextEditingController _controller;
@override
void initState() {
super.initState();
_controller = TextEditingController(text: widget.initialText);
}
@override
void deactivate() {
_unsubscribeFromDocument();
super.deactivate();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) => const SizedBox();
}

A method with no super call at all is not reported — whether the override is allowed to skip super is a different question, and this rule only orders a call that is already there.

Only subclasses of Flutter’s State are checked, unless state_base_classes names another base class.

This rule is in the recommended preset, so it is on with preset: recommended or preset: opinionated. Add it to preset: core with proper_super_calls: true.

To turn it off:

many_lints.yaml
rules:
proper_super_calls: false

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

An intermediate base class that itself extends State needs no configuration — the check walks the hierarchy. This option is for the other case: a state-like base class that does not extend Flutter’s State at all, which the rule would otherwise skip entirely.

analysis_options.yaml
many_lints:
rules:
proper_super_calls:
state_base_classes: [TrackedState]
// Not a Flutter State — its own lifecycle, same ordering contract.
abstract class TrackedState {
void initState() {}
void dispose() {}
}
class ReportPresenter extends TrackedState {
late final Stream<int> _updates;
@override
void initState() {
_updates = const Stream.empty();
super.initState(); // LINT — super must come first
}
}
Option Type Default Description
state_base_classes list of strings [] Additional non-State base classes whose subclasses should be treated as state classes