proper-controller-dispose

Same as proper_controller_dispose in Pyramid Lint

Path
lint/correctness/proper-controller-dispose
Default
On (recommended)
Origin
Pyramid Lint
Domain
flutter
Rule
422 of 512
recommendedflutter

Requires Flutter controllers owned by a State field to be disposed. Shared field checks recognize eagerly evaluated releases, including arguments and operands, but not deferred closures or conditional expression branches.

Invalid

example.dartdart
import 'package:flutter/material.dart';
import 'package:flutter/material.dart' as flutter;

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();
  }
}

class F extends State<StatefulWidget> {
  final controller = TextEditingController();

  @override
  void dispose() {
    final controller = TextEditingController();
    controller.dispose();
    super.dispose();
  }
}

class G extends State<StatefulWidget> {
  final controller = TextEditingController(text: widget.initialText);
}

class H extends flutter.State<flutter.StatefulWidget> {
  final controller = flutter.ScrollController();
}

class CollectionIfCaseInsideStatement extends State<StatefulWidget> {
  late TextEditingController controller;

  @override
  void initState() {
    super.initState();
    if (widget case final controller?) {
      final list = [if (widget case final nested?) nested];
    }
    controller = TextEditingController();
  }
}

class ShadowedBodyDisposals extends State<StatefulWidget> {
  final blockShadow = TextEditingController();
  final forShadow = TextEditingController();
  final patternShadow = TextEditingController();
  final caseShadow = TextEditingController();
  final catchShadow = TextEditingController();
  final closureShadow = TextEditingController();
  final functionShadow = TextEditingController();

  @override
  void dispose() {
    {
      final blockShadow = TextEditingController();
      blockShadow.dispose();
    }
    for (final forShadow in <TextEditingController>[]) {
      forShadow.dispose();
    }
    for (final [patternShadow] in <List<TextEditingController>>[]) {
      patternShadow.dispose();
    }
    if (widget case TextEditingController caseShadow) {
      caseShadow.dispose();
    }
    try {
      throw widget;
    } catch (catchShadow) {
      catchShadow.dispose();
    }
    final callback = (TextEditingController closureShadow) {
      closureShadow.dispose();
    };
    void local(TextEditingController functionShadow) {
      functionShadow.dispose();
    }
    super.dispose();
  }
}
This controller is never disposed; call its `dispose()` method in `dispose()`.
4class A extends State<StatefulWidget> {
5 final controller = TextEditingController();
This controller is never disposed; call its `dispose()` method in `dispose()`.
13class B extends State<StatefulWidget> {
14 final page = PageController();
This controller is never disposed; call its `dispose()` method in `dispose()`.
17class C extends State<StatefulWidget> {
18 late ScrollController scroll;
This controller is never disposed; call its `dispose()` method in `dispose()`.
32class D extends State<StatefulWidget> {
33 final tabs = TabController(length: 2, vsync: this);
This controller is never disposed; call its `dispose()` method in `dispose()`.
43class E extends State<StatefulWidget> {
44 late final anim = AnimationController.unbounded(vsync: this);
This controller is never disposed; call its `dispose()` method in `dispose()`.
52class F extends State<StatefulWidget> {
53 final controller = TextEditingController();
This controller is never disposed; call its `dispose()` method in `dispose()`.
63class G extends State<StatefulWidget> {
64 final controller = TextEditingController(text: widget.initialText);
This controller is never disposed; call its `dispose()` method in `dispose()`.
67class H extends flutter.State<flutter.StatefulWidget> {
68 final controller = flutter.ScrollController();
This controller is never disposed; call its `dispose()` method in `dispose()`.
71class CollectionIfCaseInsideStatement extends State<StatefulWidget> {
72 late TextEditingController controller;
This controller is never disposed; call its `dispose()` method in `dispose()`.
84class ShadowedBodyDisposals extends State<StatefulWidget> {
85 final blockShadow = TextEditingController();
This controller is never disposed; call its `dispose()` method in `dispose()`.
85 final blockShadow = TextEditingController();
86 final forShadow = TextEditingController();
This controller is never disposed; call its `dispose()` method in `dispose()`.
86 final forShadow = TextEditingController();
87 final patternShadow = TextEditingController();
This controller is never disposed; call its `dispose()` method in `dispose()`.
87 final patternShadow = TextEditingController();
88 final caseShadow = TextEditingController();
This controller is never disposed; call its `dispose()` method in `dispose()`.
88 final caseShadow = TextEditingController();
89 final catchShadow = TextEditingController();
This controller is never disposed; call its `dispose()` method in `dispose()`.
89 final catchShadow = TextEditingController();
90 final closureShadow = TextEditingController();
This controller is never disposed; call its `dispose()` method in `dispose()`.
90 final closureShadow = TextEditingController();
91 final functionShadow = TextEditingController();

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() {}
}

class G extends State<StatefulWidget> {
  final controller = TextEditingController();

  @override
  void dispose() {
    this.controller.dispose();
    super.dispose();
  }
}

class CollectionIfCases extends State<StatefulWidget> {
  final controller = TextEditingController();

  @override
  void dispose() {
    final value = widget;
    final list = [if (value case final controller?) controller];
    final set = <Object>{if (value case final controller?) controller};
    final map = <Object, Object>{
      if (value case final controller?) controller: controller,
    };
    controller.dispose();
    super.dispose();
  }
}

class RestoredBodyScopes extends State<StatefulWidget> {
  final controller = TextEditingController();

  @override
  void dispose() {
    {
      final controller = TextEditingController();
      controller.dispose();
    }
    for (final controller in <TextEditingController>[]) {
      controller.dispose();
    }
    for (final [controller] in <List<TextEditingController>>[]) {
      controller.dispose();
    }
    if (widget case final controller?) {
      final list = [if (widget case final nested?) nested];
    } else {
      controller.dispose();
    }
    try {
      throw widget;
    } catch (controller) {}
    final callback = (TextEditingController controller) {
      controller.dispose();
    };
    void local(TextEditingController controller) {
      controller.dispose();
    }
    controller.dispose();
    super.dispose();
  }
}

class InitializerIsNotBodyOwnership extends State<StatefulWidget> {
  final TextEditingController controller;

  InitializerIsNotBodyOwnership() : controller = TextEditingController();
}

Configure proper-controller-dispose

Set its severity in falcon.json. See the configuration guide.

falcon.jsonjson
{
  "linter": {
    "rules": {
      "correctness": {
        "proper-controller-dispose": "error"
      }
    }
  }
}

Or silence one occurrence with a suppression comment; the reason is required.

example.dartdart
// falcon-ignore lint/correctness/proper-controller-dispose: <reason>
  • proper-expanded-and-flexibleproper_expanded_and_flexiblerecommendedflutter

    Require Expanded and Flexible to sit directly inside a flex widget.

  • proper-from-environmentproper_from_environmentrecommended

    Require fromEnvironment to be evaluated in a const context.

  • proper-super-init-stateproper_super_init_staterecommendedflutter

    Require super.initState() to be the first statement of a resolved Flutter State subclass's initState override.

  • require-super-disposerecommendedflutter

    Require super.dispose() in Flutter State dispose overrides.

  • sort-pub-dependenciessort_pub_dependenciescross-file

    Require pubspec.yaml dependency sections to be sorted alphabetically.

  • test-types-in-equalstest_types_in_equalsrecommended

    Flags casting the parameter of operator == instead of testing its type.

  • throw-in-finallythrow_in_finallyrecommended

    Flags throw inside a finally block.

  • type-literal-in-constant-patterntype_literal_in_constant_patternrecommended

    Don't use constant patterns whose expressions have the core Type type.