Rules / Correctness / proper-controller-dispose
proper-controller-dispose
Require controllers owned by a State to be disposed.
Flags a controller field of a State class — a field whose type name ends in
Controller, such as TextEditingController or AnimationController — that
the State creates but never disposes. Controllers hold listeners, tickers,
and native resources that leak if they outlive the widget, so each one the
State owns must be released in its dispose method. The rule tracks fields
constructed in an initializer or assigned within the class, and checks that
each is dispose()-d (directly or via a cascade) inside dispose.
Controllers sourced from widget. are treated as owned by the parent widget
and are exempt.
Invalid
example.dartdart
import 'package:flutter/material.dart';
class A extends State<StatefulWidget> {
final controller = TextEditingController();
@override
void dispose() {
super.dispose();
}
}
class B extends State<StatefulWidget> {
final page = PageController();
}
class C extends State<StatefulWidget> {
late ScrollController scroll;
@override
void initState() {
super.initState();
scroll = ScrollController();
}
@override
void dispose() {
super.dispose();
}
}
class D extends State<StatefulWidget> {
final tabs = TabController(length: 2, vsync: this);
final text = TextEditingController();
@override
void dispose() {
text.dispose();
super.dispose();
}
}
class E extends State<StatefulWidget> {
late final anim = AnimationController.unbounded(vsync: this);
@override
void dispose() {
super.dispose();
}
}
This controller is never disposed; dispose it in the State's dispose() method.
3class A extends State<StatefulWidget> {
4 final controller = TextEditingController();
∙
This controller is never disposed; dispose it in the State's dispose() method.
12class B extends State<StatefulWidget> {
13 final page = PageController();
∙
This controller is never disposed; dispose it in the State's dispose() method.
16class C extends State<StatefulWidget> {
17 late ScrollController scroll;
∙
This controller is never disposed; dispose it in the State's dispose() method.
31class D extends State<StatefulWidget> {
32 final tabs = TabController(length: 2, vsync: this);
∙
This controller is never disposed; dispose it in the State's dispose() method.
42class E extends State<StatefulWidget> {
43 late final anim = AnimationController.unbounded(vsync: this);
∙
Valid
example.dartdart
import 'package:flutter/material.dart';
class A extends State<StatefulWidget> {
final controller = TextEditingController();
@override
void dispose() {
controller.dispose();
super.dispose();
}
}
class B extends State<StatefulWidget> {
late final controller = widget.controller;
@override
void dispose() {
super.dispose();
}
}
class C extends State<StatefulWidget> {
final scroll = ScrollController();
final text = TextEditingController();
@override
void dispose() {
scroll.dispose();
text.dispose();
super.dispose();
}
}
class D {
final controller = TextEditingController();
}
class E extends State<StatefulWidget> {
TabController? tabs;
@override
void dispose() {
tabs?.dispose();
super.dispose();
}
}
class F extends State<StatefulWidget> {
final _controller = ScrollController();
@override
void dispose() {
_controller
..removeListener(_onScroll)
..dispose();
super.dispose();
}
void _onScroll() {}
}
How to configure
Set the severity of proper-controller-dispose in your falcon.json:
falcon.jsonjson
{
"linter": {
"rules": {
"correctness": {
"proper-controller-dispose": "error"
}
}
}
}