Introduction to Dart

At its heart, Dart is a conservative programming language. It was not designed to champion bold new ideas, but rather to create a predictable and stable programming environment. The language was created at Google in 2011, with the goal of unseating JavaScript as the language of the web.

JavaScript is a very flexible language, but its lack of a type system and misleadingly simple grammar can make projects very difficult to manage as they grow. Dart aimed to fix this by finding a halfway point between the dynamic nature of JavaScript and the class-based designs of Java and other object-oriented languages. The language uses a syntax that will be immediately familiar to any developer who already knows a C-style language.

This tutorial also assumes that Dart is not your first programming language. Consequently, we will be skipping the parts of the Dart language where the syntax is the same as any other C-style language. You will not find anything in this tutorial about loops, if statements, and switch statements; they aren’t any different here from how they are treated in other languages you already know. Instead, we will focus on the aspects of the Dart language that make it unique.

In this tutorial, we will cover the following recipes, all of which will function as a primer on Dart:

  • Declaring variables – var versus final versus const
  • Strings and string interpolation
  • How to write functions
  • How to use functions as variables with closures
  • Creating classes and using the class constructor shorthand
  • Defining abstract classes
  • Implementing generics
  • How to group and manipulate data with collections
  • Writing less code with higher-order functions
  • Using the cascade operator to implement the builder pattern
  • Understanding Dart Null Safety

If you are already aware of how to develop in Dart, feel free to skip this tutorial.

Technical requirements

This tutorial will focus purely on Dart instead of Flutter. There are two primary options for executing these samples:

  • DartPad (https://dartpad.dartlang.org): DartPad is a simple web app where you can execute Dart code. It’s a great playground for trying out new ideas and sharing code.
  • IDEs: If you wish to try out these samples locally with complete code support, then you can use either Visual Studio Code or IntelliJ.

Declaring variables – var versus final versus const

Variables are user-defined symbols that hold a reference to some value. They can range from a single number to large object graphs. It is virtually impossible to write a useful program without at least one variable. You can probably argue that almost every program is ever written can be boiled down to taking in some input, storing that data in a variable, manipulating the data in some way, and then returning an output. All of this would be impossible without variables.

Recently, a new trend has appeared in programming that emphasizes immutability. This means that once the values are stored in a variable, that’s it – they cannot change. Immutable variables are safer, produce no side effects, and lead to fewer bugs as a consequence.

In this recipe, we will create a small toy program that will declare variables in the three different ways that Dart allows – varfinal, and const

Getting ready

Install the following before you get started with this recipe:

  • DartPad:
    1. In your browser, navigate to https://dartpad.dartlang.org.
  • Visual Studio Code:
    1. Double-check that the DartCode plugin has been installed.
    2. Press Command + N to create a new file and save it as main.dart.
  • IntelliJ:
    1. Double-check that you have the Dart plugin installed.
    2. Select Create new project. The following dialog will appear, asking what language and configuration you want to use: 
  1. Pick Dart as your language and then select Console Application. This effectively runs the same commands as the command-line instructions but wraps everything in a nice GUI.

When working with the code samples in this book, it is strongly discouraged that you copy and paste them into your IDE. Instead, you should transcribe the samples manually. The act of writing code, not copying/pasting, will allow your brain to absorb the code and see how tools such as code completion and DartFmt make it easier for you to type code. If you copy and paste, you’ll get a working program, but you will also learn nothing.

How to do it…

Let’s get started with our first Dart project. We will start from a blank canvas:

  1. Open main.dart and delete everything. At this point, the file should be completely empty. Now, let’s add the main function, which is the entry point for every Dart program:
main() {
variablePlayground();
}
  1. This code won’t compile yet because we haven’t defined that variablePlayground function. This function will be a hub for all the different examples in this recipe:
void variablePlayground() {
basicTypes();
untypedVariables();
typeInterpolation();
immutableVariables();
}

We added the void keyword in front of this function, which is the same as saying that this function returns nothing.

  1. Now, let’s implement the first example. In this method, all these variables are mutable; they can change once they’ve been defined:
void basicTypes() {
int four = 4;
double pi = 3.14;
num someNumber = 24601;
bool yes = true;
bool no = false;
int nothing;

print(four);
print(pi);
print(someNumber);
print(yes);
print(no);
print(nothing == null);
}

The syntax for declaring a mutable variable should look very similar to other programming languages. First, you declare the type and then the name of the variable. You can optionally supply a value for the variable after the assignment operator. If you don’t supply a value, that variable will be set to null.

  1. Dart has a special type called dynamic, which is a sort of “get out of jail free” card from the type system. You can annotate your variables with this keyword to imply that the variable can be anything. It is useful in some cases, but for the most part, it should be avoided:
void untypedVariables() {
dynamic something = 14.2;
print(something.runtimeType); //outputs 'double'
}
  1. Dart can also infer types with the var keyword. var is not the same as dynamic. Once a value has been assigned to the variable, Dart will remember the type and it cannot be changed later. The values, however, are still mutable:
void typeInterpolation() {
var anInteger = 15;
var aDouble = 27.6;
var aBoolean = false;

print(anInteger.runtimeType);
print(anInteger);

print(aDouble.runtimeType);
print(aDouble);

print(aBoolean.runtimeType);
print(aBoolean);
}
  1. Finally, we have our immutable variables. Dart has two keywords that can be used to indicate immutability – final and const

The main difference between final and const is that constmust be determined at compile-time; for example, you cannot have const containing DateTime.now() since the current date and time can only be determined at runtime, not at compile time. See the How it works… section of this recipe for more details.

  1. Add the following function to the main.dart file:
void immutableVariables() {
final int immutableInteger = 5;
final double immutableDouble = 0.015;

// Type annotation is optional
final interpolatedInteger = 10;
final interpolatedDouble = 72.8;

print(interpolatedInteger);
print(interpolatedDouble);

const aFullySealedVariable = true;
print(aFullySealedVariable);
}

How it works…

An assignment statement in Dart follows the same grammar as other languages in the C language family:

// (optional modifier) (optional type) variableName = value;
final String name = 'Donald'; //final modifier, String type

First, you can optionally declare a variable as either varfinal, or const, like so:

var animal = 'Duck';
final numValue = 42;
const isBoring = true;

These modifiers indicate whether the variable is mutable. var is completely mutable as its value can be reassigned at any point. final variables can only be assigned once, but by using objects, you can change the value of its fields. const variables are compile-time constants and are fully immutable; nothing about these variables can be changed once they’ve been assigned.

Please note that you can only specify a type when you’re using the final modifier, as follows:

final int numValue = 42; // this is ok
// NOT OK: const int or var int.

After the final modifier, you can optionally declare the variable type, from simple built-in types such as intdouble, and bool, to your own more complex custom types. This notation is standard for languages such as Java, C, C++, Objective-C, and C#.

Explicitly annotating the type of a variable is the traditional way of declaring variables in languages such as Java and C, but Dart can also interpolate the type based on its assignment. In the typeInterpolation example, we decorated the types with the var keyword; Dart was able to figure out the type based on the value that was assigned to the variable. For example, 15 is an integer, while 27.6 is a double. In most cases, there is no need to explicitly reference the type; the compiler is smart enough to figure this out. This allows us, as developers, to write succinct, script-like code and still take advantage of inherent gains that we get from a type-safe language.

The difference between final and const is subtle but important. A final variable must have a value assigned to it in the same statement where it was declared, and that variable cannot be reassigned to a different value:

final meaningOfLife = 42;
meaningOfLife = 64; // This will throw an error

While the top-level value of a final variable cannot change, its internal contents can. In a list of numbers that have been assigned to a final variable, you can change the internal values of that list, but you cannot assign a completely new list.

const takes this one step further. const values must be determined at compile-time, new values are blocked from being assigned to const variables, and the internal contents of that variable must also be completely sealed. Typically, this is indicated by having the object have a const constructor, which only allows immutable values to be used. Since their value is already determined at compile-time, const values also tend to be faster than variables.

There’s more…

In recent years, there has been a trend in development that favors immutable values over mutable ones. Immutable data cannot change. Once it has been assigned, that’s it. There are two primary benefits to preferring immutable data, as follows:

  • It’s faster. When you declare a const value, the compiler has less work to do. It only has to allocate memory for that variable once and doesn’t need to worry about reallocating if the variable is reassigned. This may seem like an infinitesimal gain, but as your programs grow, your performance gain grows as well.
  • Immutable data does not have side effects. One of the most common sources of bugs in programming is where value is changed in one place, and it causes an unexpected cascade of changes. If the data cannot change, then there will be no cascade. And in practice, most variables tend to only be assigned once anyway, so why not take advantage of immutability?

See also

Have a look at the following resources:

Strings and string interpolation

String is simply a variable that holds human-readable text. The reason why they’re called strings instead of text has more to do with history than practicality. From a computer’s perspective, a String is actually a list of integers. Each integer represents a character.

For example, the number U+0041 (Unicode notation, 65 in decimal notation) is the letter A. These numbers are stringed together to create text.

In this recipe, we will continue with the toy console application in order to define and work with strings.

Getting ready

To follow along with this recipe, you should write the code in DartPad or add the code to the existing project you created in the previous recipe, both in a new file or in the main.dart file. 

How to do it…

Just like in the previous project, you are going to create a playground function where every sub-function will demonstrate a different aspect of the strings:

  1. Type in the following code and use it as the hub for all the other string examples:
void stringPlayground() {
basicStringDeclaration();
multiLineStrings();
combiningStrings();
}
  1. The first section demonstrates the ways in which you can declare string literals. Write the following function into your code, just under the stringPlayground function:
void basicStringDeclaration() {
// With Single Quotes
print('Single quotes');
final aBoldStatement = 'Dart isn\'t loosely typed.';
print(aBoldStatement);

// With Double Quotes
print("Hello, World");
final aMoreMildOpinion = "Dart's popularity has skyrocketed with
Flutter!";
print(aMoreMildOpinion);
// Combining single and double quotes
final mixAndMatch =
'Every programmer should write "Hello, World" when learning
a new language.';
print(mixAndMatch);
}
  1. Dart also supports multi-line strings for cases where you have a text block that you want to print to the screen. The following example gets a little Shakespearean:
void multiLineStrings() {
final withEscaping = 'One Fish\nTwo Fish\nRed Fish\nBlue Fish';
print(withEscaping);

final hamlet = '''
To be, or not to be, that is the question:
Whether 'tis nobler in the mind to suffer
The slings and arrows of outrageous fortune,
Or to take arms against a sea of troubles
And by opposing end them.
''';

print(hamlet);
}
  1. Finally, one of the most common tasks programmers perform with strings is composing them to make more complex strings. Dart supports both the traditional method of concatenation, as well as a more modern method called string interpolation. Type in the following blocks of code to get a feel for both techniques:
void combiningStrings() {
traditionalConcatenation();
modernInterpolation();
}

void traditionalConcatenation() {
final hello = 'Hello';
final world = "world";

final combined = hello + ' ' + world;
print(combined);
}

void modernInterpolation() {
final year = 2011;
final interpolated = 'Dart was announced in $year.';
print(interpolated);

final age = 35;
final howOld = 'I am $age ${age == 1 ? 'year' : 'years'} old.';
print(howOld);
}
  1. Now, all we have to do to run this code is update main.dart so that it points this file to a new file. Replace the top of main.dart with the following code:
main() {
variablePlayground();
stringPlayground();
}

How it works…

Just like JavaScript, there are two ways of declaring string literals in Dart – using a single quote or double quotes. It doesn’t matter which one you use, as long as both begin and end a string with the same character. Depending on which character you chose, you would have escaped that character if you wanted to insert it in your string.

For example, to write a string stating Dart isn’t loosely typed with single quotes, you would have to write the following:

// With Single Quotes
final aBoldStatement = 'Dart isn\'t loosely typed.';

// With Double Quotes
final aMoreMildOpinion = "Dart's popularity has skyrocketed with Flutter!";

Notice how we had to write a backslash in the first example but not in the second. That backslash is called an escape character. Here, we are telling the compiler that even though it sees an apostrophe, this is not the end of the string, and the apostrophe should actually be included as part of the string.

The two ways in which you can write a string are helpful when you’re writing strings that contain single quotes/apostrophes or quotation marks. If you declare your string with the symbol that is not in your string, then you will not have to add any unnecessary characters to your code, which ultimately improves legibility.

It has become a convention to prefer single quote strings over doubles in Dart, which is what we will follow in this book, except if that choice forces us to add escape characters.

One other interesting feature of strings in Dart is multi-line strings.

If you ever had a larger block of text that you didn’t want to put into a single line, you would have to insert the newline character, \n, as you saw in this recipe’s code:

final withEscaping = 'One Fish\nTwo Fish\nRed Fish\nBlue Fish';

The newline character has served us well for many years, but more recently, another option has emerged. If you write three quotation marks (single or double), Dart will allow you to write free-form text without having to inject any non-rendering control characters, as shown in the following code block:

final hamlet = '''
To be, or not to be, that is the question:
Whether 'tis nobler in the mind to suffer
The slings and arrows of outrageous fortune,
Or to take arms against a sea of troubles
And by opposing end them.
''';

In this example, every time you press Enter on the keyboard, it is the equivalent of typing the control character, \n, in your string.

There’s more…

On top of simply declaring strings, the more common use of this data type is to concatenate multiple values to build complex statements. Dart supports the traditional way of concatenating strings; that is, by simply using the addition (+) symbol between multiple strings, like so:

final sum = 1 + 1; // 2
final concatenate = 'one plus one is ' + sum;

While Dart fully supports this method of constructing strings, the language also supports interpolation syntax. The second statement can be updated to look like this:

final sum = 1 + 1;
final interpolate = 'one plus one is $sum'

The dollar sign notation only works for single values, such as the integer in the preceding snippet. If you need anything more complex, you can add curly brackets after the dollar sign and write any Dart expression. This can range from something simple, such as accessing a member of a class, to a complex ternary operator.

Let’s break down the following example:

  final age = 35;
final howOld = 'I am $age ${age == 1 ? 'year' : 'years'} old.';
print(howOld);

The first line declares an integer called age and sets its value to 35. The second line contains both types of string interpolation. First, the value is just inserted with $age, but after that, there is a ternary operator inside the string to determine whether the word year or years should be used:

age == 1 ? 'year' : 'years'

This statement means that if the value of age is 1, then use the singular word year; otherwise, use the plural word years. When you run this code, you’ll see the following output:

I am 35 years old.

Over time, this will become natural. Just remember that legible code is usually better than shorter code, even if it takes up more space.

It’s probably worth mentioning another way to perform concatenation tasks, which is using the StringBuffer object. Consider the following code:

List fruits = ['Strawberry', 'Coconut', 'Orange', 'Mango', 'Apple'];
StringBuffer buffer = StringBuffer();
for (String fruit in fruits) {
buffer.write(fruit);
buffer.write(' ');
}
print (buffer.toString()); // prints: Strawberry Coconut Orange Mango Apple

You can use a StringBuffer to incrementally build a string. This is better than using string concatenation as it performs better. You add content to a StringBuffer by calling its write method. Then, once it’s been created, you can transform it into a String with the toString method.

See also

Check out the following resources for more details on strings in Dart:

How to write functions

Functions are the basic building blocks of any programming language and Dart is no different. The basic structure of a function is as follows:

optionalReturnType functionName(optionalType parameter1, optionalType parameter2...) {
// code
}

You have already written a few functions in previous recipes. In fact, you really can’t write a functioning Dart application without them.

Dart also has some variations of this classical syntax and provides full support for optional parameters, optionally named parameters, default parameter values, annotations, closures, generators, and asynchronicity decorators. This may seem like a lot to cover in one recipe, but with Dart, most of this complexity will disappear.

Let’s explore how to write functions and closures in this recipe. 

Getting ready

To follow along with this recipe, you can write the code in DartPad, or add the code to the existing project you created in the previous recipe, either in a new file or in the main.dart file. 

How to do it…

We’ll continue with the same pattern from the previous recipe:

  1. Start by creating the hub function for the different features we are going to cover:
void functionPlayground() {
classicalFunctions();
optionalParameters();
}
  1. Now, add some functions that take parameters and return values:
void printMyName(String name) {
print('Hello $name');
}

int add(int a, int b) {
return a + b;
}

int factorial(int number) {
if (number <= 0) {
return 1;
}

return number * factorial(number - 1);
}

void classicalFunctions() {
printMyName('Anna');
printMyName('Michael');

final sum = add(5, 3);
print(sum);

print('10 Factorial is ${factorial(10)}');
}
  1. One of the new features that Dart has added is optional parameters. If you wrap your function’s parameter list in square brackets, then those parameters can be omitted without the compiler throwing errors. 

The question mark after a parameter, such as in String? name, tells the Dart compiler that the parameter itself can be null

  1. Write this code immediately after the previous example:
void unnamed([String? name, int? age]) {
final actualName = name ?? 'Unknown';
final actualAge = age ?? 0;
print('$actualName is $actualAge years old.');
}

Dart also supports named optional parameters, with curly brackets.When calling a function with named parameters, you need to specify the parameter name. You can call the parameters in any order; for example, named(greeting: 'hello!');.

  1. Add this function right after the unnamed optional function:
void named({String? greeting, String? name}) {
final actualGreeting = greeting ?? 'Hello';
final actualName = name ?? 'Mystery Person';
print('$actualGreeting, $actualName!');
}
  1. Optional parameters and optional named parameters also support default values. If the parameter is omitted when the function is called, the default value will be used instead of null. You can also place a set of required parameters first, followed by a list of optionals. Add the following code to see how this can be accomplished:
String duplicate(String name, {int times = 1}) {
String merged = '';
for (int i = 0; i < times; i++) {
merged += name;
if (i != times - 1) {
merged += ' ';
}
}

return merged;
}
  1. Now, implement the playground function to show all these pieces in action:
void optionalParameters() {
unnamed('Huxley', 3);
unnamed();

// Notice how named parameters can be in any order
named(greeting: 'Greetings and Salutations');
named(name: 'Sonia');
named(name: 'Alex', greeting: 'Bonjour');

final multiply = duplicate('Mikey', times: 3);
print(multiply);
}
  1. Finally, update the main method so that these functions can be executed:
main() {
variablePlayground();
stringPlayground();
functionPlayground();
}

How it works…

With Dart, you can write functions with unnamed (the old way), named, and unnamed optional parameters. In Flutter, unnamed optional parameters are the most common style you will be using, especially with widgets.

Named parameters can also remove ambiguity from what each parameter is supposed to do. Take a look at the following line from the preceding code example:

unnamed('Huxley', 3);

Now, compare it with this line:

duplicate('Mikey', times: 3);

In the first example, it isn’t immediately clear what the purpose of each parameter is. In the second example, the times parameter immediately tells you that the text Mikey will be duplicated three times. This can go a long way with functions that have rather long parameter lists, where it can be difficult to remember the expected order of the parameters. Take a look at how this syntax is put to work in the Flutter framework:

Container(
margin: const EdgeInsets.all(10.0),
color: Colors.red,
height: 48.0,
child: Text('Named parameters are great!'),
)

This isn’t even all the properties that are available for containers – it can get much longer. Without named parameters, this sort of syntax could be almost impossible to read. Type annotation for Dart functions is optional.

You can completely omit it if you are so inclined. However, for any parameter or even function name that does not have type annotation, Dart will assume that it is of the dynamic type. Since we would like to exploit Dart’s type system for all its worth, dynamic types should be avoided. That is why we always strive to add the void keyword in front of any function that doesn’t return a value.

Written by

XR Developer responsible for end-to-end development of XR solutions spanning multiple domains, by using various XR and WebXR libraries.

Leave a Reply