Skip to content

Proposal: initialize fields at the declaration site #153644

Open

Description

@nate-thegrate

before

class _MyState extends State<MyWidget> with SingleTickerProviderMixin {
  late final int _value;
  AnimationController? _controller;

  @override
  void initState() {
    _value = widget.value;
    _controller = AnimationController(
      duration: Durations.medium1,
      vsync: this,
    );
  }

  // ...
}

after

class _MyState extends State<MyWidget> with SingleTickerProviderMixin {
  late final int _value = widget.value;

  late final AnimationController _controller = AnimationController(
    duration: Durations.medium1,
    vsync: this,
  );

  // ...
}

This has a few advantages:

  1. More concise
  2. Less jumping around to see how fields are initialized
  3. Less prone to late initialization errors
  4. The prefer final & unnecessary nullable lints will kick in

Activity

  1. added
    frameworkflutter/packages/flutter repository. See also f: labels.
    c: proposalA detailed proposal for a change to Flutter
    P3Issues that are less important to the Flutter project
    c: tech-debtTechnical debt, code quality, testing, etc.
    refactorImproving readability/efficiency without behavioral changes
    on Aug 18, 2024
  2. moffatman commented on Aug 18, 2024

    @moffatman
    Contributor

    I thought it was this way because of some code path where the state object could be created. But never actually mounted. Therefore would not be dispose()ed. However I think you're right upon looking into it.... this should be safe

  3. jonahwilliams commented on Aug 19, 2024

    @jonahwilliams
    Contributor

    This is not always a safe change. late finals are not initialized until accessed. Simple example, the following code will never update.

    class FooState extends State<Foo> {
      late final timer = Timer.periodic(Duration(milliseconds: 10), (timer) {
        setState(() {
          count++;
        });
      });
      int count = 0;
    
      Widget build(_) => Text('Count $cound');
    }
    
  4. nate-thegrate commented on Aug 19, 2024

    @nate-thegrate
    ContributorAuthor

    Yeah, forgot to mention that, but it's a good point—there was one time when I tried subscribing to a Firebase stream using a late final variable, and never actually subscribed to it 😒

  5. nate-thegrate commented on Aug 23, 2024

    @nate-thegrate
    ContributorAuthor

    some code path where the state object could be created but never actually mounted… However I think you're right upon looking into it

    I wanted to double-check as well 🙂
    Fortunately, it does look like every State object is mounted when it's created:

    final Element newChild = newWidget.createElement();
    assert(() {
    _debugCheckForCycles(newChild);
    return true;
    }());
    newChild.mount(this, newSlot);


    But recently I learned that I'd missed something important—there are actually 2 potential dangers involving late fields.

    late initialization errors

    Happens when a late variable is referenced before being initialized; can be avoided by initializing at the declaration site.

    calling createTicker() inside dispose()

    AnimationController calls vsync.createTicker() in its constructor, so if a late controller isn't referenced until it's being disposed of, it can result in an error.

    For example, you could have a method that references the controller, but the controller won't be initialized until if/when the method is called:

    class _MyState extends State<MyWidget> with TickerProviderMixin {
      late final _controller = AnimationController(vsync: this, duration: Durations.long1)
        ..addListener(() => setState(() {}));
    
      void _open() {
        _controller.forward();
      }
    
      @override
      void dispose() {
        _controller.dispose(); // dangerous
        super.dispose();
      }
    
      // ...
    }
  6. flutter-triage-bot commented on Feb 1, 2025

    @flutter-triage-bot

    This issue is assigned to @nate-thegrate but has had no recent status updates. Please consider unassigning this issue if it is not going to be addressed in the near future. This allows people to have a clearer picture of what work is actually planned. Thanks!

  7. added
    ⏳Bot is counting down the days until it unassigns the issue
    on Feb 1, 2025
  8. flutter-triage-bot commented on Mar 10, 2025

    @flutter-triage-bot

    This issue was assigned to @nate-thegrate but has had no status updates in a long time. To remove any ambiguity about whether the issue is being worked on, the assignee was removed.

  9. removed
    ⏳Bot is counting down the days until it unassigns the issue
    on Mar 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Issues that are less important to the Flutter projectc: proposalA detailed proposal for a change to Flutterc: tech-debtTechnical debt, code quality, testing, etc.frameworkflutter/packages/flutter repository. See also f: labels.refactorImproving readability/efficiency without behavioral changesteam-frameworkOwned by Framework teamtriaged-frameworkTriaged by Framework team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions