dispose_fields
Flags instance fields in State subclasses whose type has a dispose(), close(), or cancel() method but the field is not cleaned up in the widget’s dispose() method. Common types include TextEditingController, FocusNode, AnimationController, StreamController, StreamSubscription, and Timer.
Why use this rule
Section titled “Why use this rule”Disposable resources that are not cleaned up cause memory leaks. A TextEditingController that is never disposed keeps its listeners and internal state alive indefinitely. This rule checks that every field with a cleanup method has a matching call in dispose(), catching missing or incomplete cleanup.
See also: State.dispose() | Dart lint: cancel_subscriptions | Dart lint: close_sinks
class _BadState extends State<BadWidget> { final _textController = TextEditingController(); final _focusNode = FocusNode(); final _streamController = StreamController<int>();
// No dispose() method -- all fields leak
@override Widget build(BuildContext context) => const SizedBox();}
class _IncompleteState extends State<IncompleteWidget> { final _controller1 = TextEditingController(); final _controller2 = TextEditingController();
@override void dispose() { _controller1.dispose(); // _controller2 is missing! super.dispose(); }
@override Widget build(BuildContext context) => const SizedBox();}class _GoodState extends State<GoodWidget> { final _textController = TextEditingController(); final _focusNode = FocusNode();
@override void dispose() { _textController.dispose(); _focusNode.dispose(); super.dispose(); }
@override Widget build(BuildContext context) => const SizedBox();}
class _StreamState extends State<StreamWidget> { final _streamController = StreamController<int>();
@override void dispose() { _streamController.close(); super.dispose(); }
@override Widget build(BuildContext context) => const SizedBox();}Turning this rule off
Section titled “Turning this rule off”This rule is in the core preset, so it is on with preset: core,
preset: recommended or preset: opinionated.
To turn it off:
rules: dispose_fields: falseTo keep the rule on but skip certain paths, use per-rule exclude.
Options
Section titled “Options”many_lints: rules: dispose_fields: additional_cleanup_methods: [release, shutdown]rules: dispose_fields: additional_cleanup_methods: [release, shutdown]| Option | Type | Default | Description |
|---|---|---|---|
cleanup_methods |
list of strings | [dispose, close, cancel] |
Replaces the cleanup method names the rule looks for |
additional_cleanup_methods |
list of strings | [] |
Extends whichever list applies |
state_base_classes |
list of strings | [] |
Extra base classes treated as state. Only needed for a base that does not extend Flutter’s State; an intermediate BaseState<T> is already recognised |
Order matters: it is the priority used when a type declares more than one
cleanup method. Names added via additional_cleanup_methods are appended, so a
project’s own release() is only chosen when the type declares no standard
cleanup method.
Both options apply to detection and recognition — a method listed here counts both as “this field needs cleaning up” and as “this call cleans it up”.
Related rules
Section titled “Related rules”always_remove_listener— Ensure every addListener() has a matching removeListener() in dispose().avoid_unremovable_callbacks_in_listeners— Don’t pass an inline closure to addListener.dispose_provided_instances— Ensure disposable instances in Riverpod providers are cleaned up with ref.onDispose.avoid_late_final_reassignment— Flag alate finalfield assigned twice on one path.