Dart - Variable Keywords

This tutorial explains the meaning of variable keywords in Dart which include var, final, const, static, and dynamic.

Using var Keyword

var is used to declare a variable with inferred type. If you declare a variable using the var keyword, Dart will infer the type according to the initializer type. If there is no initializer, the type will be set to dynamic.

A variable declared using var can be reassigned. However, you cannot assign a value of different type unless it's declared with no initializer in which the type is set to dynamic.

  var text1 = 'Hello world'; // String
  text1 = 'Hello world from Woolha.com';
  text1 = 1; // Error: A value of type 'int' can't be assigned to a variable of type 'String'. (compile-time)

  var text2; // dynamic
  text2 = 2;
  text2 = 'Two';

In the code above, text1 is assigned with a String value. The declaration statement is actually the same as String text1 = 'Hello world';. It can be reassigned with another String value. However, if you try to assign a value of another with type, the compiler will give you an error.

Meanwhile, the text2 variable doesn't have an initial value. As a result, Dart sets the variable to have a dynamic type. Therefore, it can be reassigned with another value of any type.

We can conclude that the initializer type determines the type of a variable declared using the var keyword. If you prefer to initialize variables without explicitly stating the types, using the var keyword is the solution.

In addition, it also makes it less likely to remove the generic part of a type. For example, if you create a List variable whose elements are integers, the type should be List<int>. If you explicitly write the types, you may forget to write the generic type. As a result, the variable type becomes List<dynamic> instead. It will not happen if you use var because Dart can detect that the list contains integer only values and therefore the type is set to List<int>.

  List numbers1 = [1, 2, 3]; // List<dynamic>
  var numbers2 = [1, 2, 3]; // List<int>

var is useful for declaring variables with a small scope whose value can be reassigned later. For example, the variables inside a method. However, for class members and method parameters, it's recommended to explicitly state the types.

Using final Keyword

The final keyword means the variable cannot be reassigned. If you try to reassign it, the compiler will give you an error.

To declare a variable using the final keyword, you can either explicitly state the type or let Dart infer the type. The type inference is similar to the var keyword. It depends on the type of the initializer value. It can be a dynamic type if there is no initializer.

  final DateTime currentTime1 = DateTime.now();
  currentTime1 = DateTime.now(); // Error. The final variable 'currentTime1' can only be set once. (compile-time)

  final currentTime2 = DateTime.now(); // DateTime

  final currentTime3; // dynamic

final only prevents a variable to be reassigned. It doesn't guarantee that the variable cannot be modified. In the example below, despite the List variable being declared using the final keyword, it's still possible to modify the List value.

  final scores = [80, 90, 100];
  scores.remove(80);
  print(scores); // [90, 100]

If you have a variable that cannot be reassigned, it would be better to use final to avoid accidental reassignment. final is suitable for cases where the value cannot be determined at the compile time. For example, the current time value can only be determined when the code is executed.

Using const Keyword

const can be used for variables whose value can be determined at compile time and cannot be reassigned . Therefore, it's usually used to store hard-coded constant values.

Similar to using const, you can explicitly write the type or not. In the latter case, Dart will infer the variable type based on the initializer value. The difference is you cannot define a const with no initializer value.

  const Duration readTimeout = Duration(seconds: 30);
  readTimeout = Duration(seconds: 300); // Constant variables can't be assigned a value.
  
  const connectTimeout = Duration(seconds: 10);
  connectTimeout = Duration(seconds: 100); // Constant variables can't be assigned a value.
  
  const timeout; // Error: The constant 'timeout' must be initialized. (compile-time)

The usage of const is related to object canonicalization, which affects how object comparison works in Dart. You may already know about the const constructors. By defining a const constructor, two objects created using the same constructor argument values will only be created once. They will refer to the same object in the memory. Proper usage of the const keyword may affect the performance of your application.

  class Person {
    final String name;
  
    const Person({required this.name});
  }

  main() {
      const person = Person(name: 'Ivan');
  }

That capability is also utilized a lot by Flutter. In Flutter, there are a lot of widgets with a const constructor. If a widget is created using a const constructor, Flutter may prevent that particular widget from being rebuilt when the parent tree is being rebuilt.

Using static Keyword

In Dart, the static keyword is used on class data members. It can be used on class variables and class methods. By declaring a class member using the static keyword, Dart will only create it once and it's the same across all instances of the class. In addition, you don't need to create an instance of the class to access it.

Below is the example of a static variable. To define a static variable, you can write the static keyword followed by the var, final, const or the explicit variable type before the variable name.

  class MyHelper {
    static int counter = 0;
  
    void doSomething() {
      counter++;
      print('counter: $counter');
    }
  }

The class above has a static variable counter. There is a method named doSomething that increments the counter value and prints the current value. Let's see what happens if we have two instances of MyHelper class.

  var helper1 = MyHelper();
  helper1.doSomething(); // counter: 1
  var helper2 = MyHelper();
  helper2.doSomething(); // counter: 2

The result is, inside helper2.doSomething(), the counter value is 2 despite helper2 only calls the doSomething method once. That's because all instances of MyHelper class share the same counter variable.

It's also very common to combine the static keyword with the const keyword for declaring constant values. If you want to create a class that stores constant values, it's also better to make the class to not have any instance.

Using dynamic Keyword

The dynamic keyword is used to disable static type-checking. That allows you to call any method on a variable with dynamic type. The checking will be done at runtime instead of compile time. Therefore, it's possible that your code compiled successfully but gets an error later when the code is executed.

A variable with the dynamic keyword can be reassigned with a value of any type. To check the type of the value that's currently assigned to a dynamic variable, you can check the runtimeType property of the object.

The dynamic keyword can be preceded by final or const. In that case, the variable cannot be reassigned.

  dynamic x = 1;
  print(x.runtimeType);
  x = 'a';
  print(x.runtimeType);
  int x2 = x as int; // type 'String' is not a subtype of type 'int' in type cast (runtime)

  const dynamic y = 2;
  y = 20; // Error: Constant variables can't be assigned a value. (compile-time)

  final dynamic z = 3;
  z = 30; // Error: The final variable 'z' can only be set once. (compile-time)

Summary

In this tutorial, we have learned the meaning of var, final, const, static, and dynamic keywords in Dart programming language. Having understood those keywords, you should be able to determine which one to use when declaring a variable.

You can also read about: