Flutter: Lazy instantiation with the `late` keyword

With the introduction of NNBD in Dart 2.12, a new keyword was created: late. The primary reason this was created was to allow for non-null fields, that did not have to be immediately initialized.

// Allow this to be null for now, compiler trusts us, that we will assign it.
late final int i; 
MyConstructor(){
   i = getStartIndex(); // Compiler checks at this point, everything is ok!
   i = null; // This will cause an error, compiler is enforcing non-null
}

This was a necessary feature/workaround for Flutter, because of darts requirement to use const initializers, and most Flutter developers are likely familiar with it by now. In this post we’re going to look at some of the other benefits of late!

Lets get lazy…

late has another great application for your Flutter code: you can remove many of your initState/constructor calls!

This is because late runs “lazily”, which means it is not run at all until it is referenced the first time. This might sound un-important, but it has implications that are quite nice for general use cases: your field initializers code can access other fields, methods and .this!

This totally unlocks your initializer code to be more flexible. Typically you can not access other fields, or methods on the class instance:

However, when using late, these all work fine!

How does this relate to Flutter?

Previously if we’re making an AnimationController, this would have to be done in initState or constructor, because this, which is required by vsync can only be accessed from a method.

  AnimationController anim1;

  @override
  void initState() {
    super.initState();
    anim1 = AnimationController(vsync: this, duration: Duration(seconds: 1))..forward();
  }

Now, we can just write it like this:

late AnimationController anim = AnimationController(vsync: this, duration: Duration(seconds: 1))..forward();

This saves you 6 lines of boilerplate 🙂

You can use this to fetch initial values from calculation methods, or setting any default that depends on a dynamic value.

You can also make builder methods, and call them:

  late AnimationController anim1 = createAnim(seconds: 1, play: true);
  late AnimationController anim2 = createAnim(seconds: 2);
  late AnimationController anim3 = createAnim(seconds: 3);

  AnimationController createAnim({required int seconds, bool play = false}) {
    final c = AnimationController(vsync: this, duration: Duration(seconds: seconds));
    if(play) c.forward();
    c.addListener(() => setState((){}));
    return c;
  }

No need for initState, no @override or super() boilerplate either, every line has meaning.

What about widget parameters?

Unfortunately this does not work for default constructor params, so something like this still does not work:

class Foo extends StatelessWidget {
  late final FocusNode focusNode; 
  // Compiler will complain this is not a const
  Foo({Key? key, this.focusNode = FocusNode()}) : super(key: key);
}

Hopefully this lazy instantiation could in the future be extended to work for constructor args, but in the meantime this is still a really nice addition to the dart developers toolbox.

Happy coding!

shawn.blais

Shawn has worked as programmer and product designer for over 20 years, shipping several games on Steam and Playstation and dozens of apps on mobile. He has extensive programming experience with Dart, C#, ActionScript, SQL, PHP and Javascript and a deep proficiency with motion graphics and UX design. Shawn is currently the Technical Director for gskinner.

@tree_fortress

3 Comments

  1. Hi, its a good subject. But, when I use late keyword it says Unexpected text ‘late’.
    my dart version is 2.12.2. I tried it in a new project but it doesn’t work. Also try ‘flutter clean’

  2. Hey thanks for the article! Custom animations seem way less tedious!

  3. Ahmet:

    Make sure that in the pubspec.yaml you have the sdk greater or equal to 2.12

    environment:
    sdk: “>=2.12.0 <3.0.0"

Leave a Reply

Your email address will not be published. Required fields are marked *