Rules / Correctness / proper-controller-dispose

proper-controller-dispose

lint/correctness/proper-controller-dispose
recommendedflutter
Same as proper_controller_dispose · Pyramid Lint

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"
      }
    }
  }
}