Dart Const Tutorial – All You Need to Know (Const Expressions, Canonical Instances and More)

1  comments

Constants are not just an obnoxious version of final variables that will haunt you in your sleep with all of the errors associated with them. Compile-time constants are actually a nice way to improve the performance of your app by not instantiating the same object multiple times and additionally, so to say, "pre-instantiating" objects at compile time.

Const or Final?

Let's get this question out of the way before moving on to exploring constants in depth. It seemingly doesn't matter whether you have a const or final keyword before a variable. These variables simply cannot be modified after their declaration.

main.dart

void run() {
  const myConstNumber = 5;
  final myFinalNumber = 5;

  myConstNumber = 42; // error
  myFinalNumber = 42; // error
}

final is additionally suited for more occasions while const can be used only for top-level, static or local variables. That means no const instance fields.

main.dart

class MyClass {
  final int myFinalField;
  const int myConstField; // error

  MyClass(this.myFinalField, this.myConstField);
}

To avoid a taxing decision-making process whenever you want to create a variable, many developers simply choose final every time without even thinking about const. I hope this will change once you see the benefits of canonical instances. But first...

Built-in constants

Every Dart type with a literal is practically constant. Writing string literals such as 'hello' or numeric literals like 3.14 directly into the code naturally creates objects which are known at compile time.

What's more, even collection literals can be put into constant variables.

main.dart

void run() {
  const myList = [1, 2, 3];
  const myMap = {'one': 1};
  const mySet = {1, 2, 3};
}
Collection if and for, the ... spread operator, type checking and casting is also supported inside constant collection literals.

But again, much like with numeric and string literals you can just as well put collection literals into final fields. ?

main.dart

void run() {
  final myList = [1, 2, 3];
  final myMap = {'one': 1};
  final mySet = {1, 2, 3};
}

Compiler's view on constants

As you could already see, there is literally no difference between const and final variables from the programmer's point of view, other than the fact that constants are harder to work with. The Dart compiler has a totally different pair of eyes though and it sees a huge difference, best illustrated on a code snippet. The highlighted parts below are seen as constant to the compiler.

main.dart

void run() {
  final finalVariable = 123456;
  const constVariable = 123456;
}

It may not be apparent at first glance but there is a big benefit to the whole field being constant instead of just the value being constant. Constant variables can be used further down the line in other places requiring constants.

main.dart

void run() {
  final finalVariable = 123456;
  const constVariable = 123456;

  const notWorking = finalVariable; // error
  const working = constVariable;
}

Assigning constant variables to other constant variables may not be something you do on a daily basis but there is one thing where passing around constant variables comes in handy...

Const constructors

Many Flutter classes have const constructors, for example, the EdgeInsets class used for Padding. This is extremely useful performance-wise because of what is known as canonical instances.

Writing const EdgeInsets.all(8) hundreds of times throughout your app doesn't clutter up the memory with hundreds of different instances. Instead, whenever you use the same arguments for a const constructor or a factory, the same "canonical" instance will be reused.

You can also create your own class with a const constructor. The only rule is: Constant class fields must be final and capable of being constant themselves.

main.dart

class MyConstClass {
  final int field1;
  final List<String> field2;
  const MyConstClass(this.field1, this.field2);
}

void run() {
  // passing in a const variable
  const constVariable = 123456;
  const x = MyConstClass(constVariable, ['hello', 'there']);
}

This means, you cannot have a field of a type which doesn't have a const constructor. Sort of...

main.dart

class MyWannabeConstClass {
  // Future doesn't have a const constructor or a const factory
  final Future<int> field;
  // Dart allows us to define a seemingly nonsensical constructor:
  const MyWannabeConstClass(this.field);
}

void run() {
  // Dart doesn't allow us to use the const constructor:
  const x = MyWannabeConstClass(Future.value(123)); // error
}

So why does Dart even allow us to define a constructor which seems to always fail? Well, because it won't fail if you say so. Class having a const constructor doesn't mean you always have to get canonical instances. You can also instantiate new instances as usual.

main.dart

void run() {
  // Passing in a Future doesn't give errors with a non-const constructor
  final implicitNew = MyWannabeConstClass(Future.value(123));
  final explicitNew = new MyWannabeConstClass(Future.value(123));
}
While creating non-canonical new instances of a class with a const constructor is possible, it's rather discouraged as you lose out on all the amazing performance benefits of reusing the same instance possibly thousands of times in a big Flutter app.

Const context

As you can see above, the sole fact of changing a const variable into a final variable automatically creates a "new" instance.

Much like the new keyword is optional when instantiating new instances, the const keyword is also optional when trying to get existing canonical instances.

There is absolutely no difference in the following two lines:

main.dart

void run() {
  const implicitConst = MyConstClass(1);
  const explicitConst = const MyConstClass(1);
}

But there is still a place for explicitly invoking the const constructor when you want to store a constant, canonical instance inside a non-constant variable.

main.dart

void run() {
  final regularVariable = const MyConstClass(1);
}

Const = ?

This tutorial has hopefully demystified the meaning of const constructors and Dart constants in general. Try using const whenever possible and you will bring small performance improvements into your apps line by line. And you know how it is with small improvements - they add up.

About the author 

Matt Rešetár

Matt is an app developer with a knack for teaching others. Working as a Flutter Developer at LeanCode and a developer educator, he is set on helping other people succeed in their Flutter app development career.

You may also like

Flutter UI Testing with Patrol

Flutter UI Testing with Patrol
  • {"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}
    >