Write BETTER code with Dart 2.17 (Included in Flutter 3)

Write BETTER code with Dart 2.17 (Included in Flutter 3)

Flutter 3 was announced in the last Google I/O event, and with it the possibility of being able to deploy desktop applications on macOS and Linux in a stable way. They also showed the new performance improvements that this new version brings, and regarding the programming language of Flutter, Dart, version 2.17 was officially presented; which is the version included by default in new projects created with Flutter 3.

📽 Video version available on YouTube and Odysee

In this blog I have previously covered the incredible performance improvements affecting Flutter for web apps and the possibility of running macOS applications natively in Apple Silicon processors. In this article I am going to delve into the most notable improvements that Dart 2.17 brings, which is the enhancement of enums, the super initializers and the removal of order constraint for named parameters.

In order to easily see and understand what are these improvements, I am going to present you a very simple application created with Flutter 2. Then I am going to update it to Flutter 3 and I will show you the changes that we should make to this application to apply each one of these improvements, in this graphical way you will easily understand what are those changes and how you can benefit from them in your applications developed with Flutter 3 and Dart 2.17.

To start with, I'll show you the test application I've created in the previous version of Flutter, version 2.10.5:

Flutter 2

This is the test application:

app.png

It consists of 3 different parts painted in a different color, with the name of the color in the center of the container.

To determine the color and its name I use the following enum:

import 'package:flutter/material.dart';

enum MyColor {
  red,
  green,
  blue,
}

extension MyColorExtension on MyColor {
  String getName() {
    switch (this) {
      case MyColor.red:
        return 'Red';
      case MyColor.green:
        return 'Green';
      case MyColor.blue:
        return 'Blue';
    }
  }

  MaterialColor getMaterialColor() {
    switch (this) {
      case MyColor.red:
        return Colors.red;
      case MyColor.green:
        return Colors.green;
      case MyColor.blue:
        return Colors.blue;
    }
  }
}

As you can see, this enum defines what color it is, and through an extension I add its name and what MaterialColor it refers to. I will use these values ​​later to be able to paint the container and to display the name of the color.

Before Dart 2.17, the only way to be able to add values ​​to an enum is by using extensions.

Each of the three fragments shown in the previous screenshot is represented by the following widget:

class ColorWidget extends StatelessWidget {
  final MyColor color;

  const ColorWidget(this.color, {Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Expanded(
      child: Container(
        color: color.getMaterialColor(),
        child: Center(
          child: Text(
            color.getName(),
            style: const TextStyle(
              color: Colors.white,
              fontWeight: FontWeight.bold,
              fontSize: 24.0,
            ),
          ),
        ),
      ),
    );
  }
}

This widget receives a value from the previously created enum and uses it to paint the background of the container and display the name.

Finally, this widget is used in the following way:

import 'package:flutter/material.dart';
import 'package:flutter_3_dart_2_17/color.dart';

void main() {
  runApp(const App());
}

class App extends StatelessWidget {
  const App({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(home: MainScreen());
  }
}

class MainScreen extends StatelessWidget {
  const MainScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Dart 2.17 example'),
      ),
      body: Column(
        children: const [
          ColorWidget(
            MyColor.red,
            key: Key('Red'),
          ),
          ColorWidget(
            MyColor.green,
            key: Key('Green'),
          ),
          ColorWidget(
            MyColor.blue,
            key: Key('Blue'),
          ),
        ],
      ),
    );
  }
}

So that you can appreciate the changes that we will make later, note that the key parameter that the widgets receive has to be passed to the super class in the way described in this example:

const App({Key? key}) : super(key: key);

We will change this later so that we will do the same thing with less code. Also in this case when we declare a ColorWidget we have to pass the key parameter at the end, since it is a named parameter. If I try to alter the order of the parameters the compiler will give me an error:

// In previous versions of Dart, named parameters
// have to be at the end
ColorWidget(
  key: Key('Blue'),
  MyColor.blue, // <- This throws an error
),

The next step is to upgrade to Flutter 3 and start using Dart 2.17 to apply the new improvements.

Flutter 3 and Dart 2.17

After executing a flutter upgrade to update to Flutter 3, the next thing we need to do is increase the minimum SDK level in pubspec.yaml in order to use Dart 2.17:

environment:
  sdk: ">=2.17.0 <3.0.0"

Next, we are going to apply the following changes to the previous enum MyColor:

import 'package:flutter/material.dart';

enum MyColor {
  red('red', Colors.red),
  green('green', Colors.green),
  blue('blue', Colors.blue);

  final String name;
  final MaterialColor materialColor;

  const MyColor(this.name, this.materialColor);
}

As you can see, we can define two variables, name and materialColor; which are passed by constructor on each value. In this way we can do the same job as before but in a cleaner and more elegant way, with less code, and without the need to use extension functions.

Now let's do the following to ColorWidget:

class ColorWidget extends StatelessWidget {
  final MyColor color;

  const ColorWidget(this.color, {super.key});

  @override
  Widget build(BuildContext context) {
    return Expanded(
      child: Container(
        color: color.materialColor,
        child: Center(
          child: Text(
            color.name,
            style: const TextStyle(
              color: Colors.white,
              fontWeight: FontWeight.bold,
              fontSize: 24.0,
            ),
          ),
        ),
      ),
    );
  }
}

Here we are using the variables created earlier, for example before we got the name of the color with color.getName() now we just have to do color.name.

Also note the change in the key parameter passed in the constructor. With dart 2.17 we can simply pass this parameter to the super class via super.key. In this case the change is small, but let's imagine a constructor like this:

class ClassThatExtends extends SuperClass {
  ClassThatExtends({
    required int firstInt,
    required int secondInt,
    required int thirdInt,
    required int fourthInt,
    required int fifthInt,
    required int sixthInt,
    required int seventhInt,
  }) : super(
          firstInt: firstInt,
          secondInt: secondInt,
          thirdInt: thirdInt,
          fourthInt: fourthInt,
          fifthInt: fifthInt,
          sixthInt: sixthInt,
          seventhInt: seventhInt,
        );
}

After using this new enhancement, it would look like this:

class ClassThatExtends extends SuperClass {
  ClassThatExtends({
    required super.firstInt,
    required super.secondInt,
    required super.thirdInt,
    required super.fourthInt,
    required super.fifthInt,
    required super.sixthInt,
    required super.seventhInt,
  });
}

To finish, we will modify the rest of the widgets as follows:

import 'package:flutter/material.dart';
import 'package:flutter_3_dart_2_17/color.dart';

void main() {
  runApp(const App());
}

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(home: MainScreen());
  }
}

class MainScreen extends StatelessWidget {
  const MainScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Dart 2.17 example'),
      ),
      body: Column(
        children: const [
          ColorWidget(
            key: Key('Red'),
            MyColor.red,
          ),
          ColorWidget(
            key: Key('Green'),
            MyColor.green,
          ),
          ColorWidget(
            key: Key('Blue'),
            MyColor.blue,
          ),
        ],
      ),
    );
  }
}

In addition to applying the super constructor enhancement, notice that I've altered the order of the key parameters and the associated MaterialColor in the ColorWidget. In Dart 2.17 we can place the named parameters in any order we see fit. In this simple example the order has little influence, but let's take a look at the example the Flutter team used:

final factorials = List<int>.generate(
  10,
  (int i) {
    if (i == 0) {
      return 1;
    } else {
      var result = 1;
      for (var r = 2; r <= 1; ++r) {
        result *= r;
      }
      return result;
    }
  },
  // This param could be difficult to see due to the code block above
  growable: true,
);

Here the growable parameter is hard to see, after applying this tweak we can place it earlier to make it easier for the reader to understand that it's there:

final factorials = List<int>.generate(
  10,
  growable: true,
  (int i) {
    if (i == 0) {
      return 1;
    } else {
      var result = 1;
      for (var r = 2; r <= 1; ++r) {
        result *= r;
      }
      return result;
    }
  },
);

Conclusion

In this article we have seen 3 of the improvements applied in Dart 2.17. If you want, you can take a look at the official blog post to see all these improvements and the rest of the new features that this new version of Dart offers.

You can find the sample of this article here.

Thank you very much for reading this far.

Did you find this article valuable?

Support David Serrano by becoming a sponsor. Any amount is appreciated!