An excerpt from Dart: Up and Running

Chapter 2. A Tour of the Dart Language

This chapter shows you how to use each major Dart feature, from variables and operators to classes and libraries, with the assumption that you already know how to program in another language.

Note

To play with each feature, create a command-line application project in Dart Editor, as described in the section called “Up and Running”.

Consult the Dart Language Specification whenever you want more details about a language feature.

A Basic Dart Program

The following code uses many of Dart’s most basic features:

lang-dart
// Define a function.
printNumber(num aNumber) {
  print('The number is $aNumber.'); // Print to the console.
}

// This is where the app starts executing.
main() {
  var number = 42;           // Declare and initialize a variable.
  printNumber(number);       // Call a function.
}

Here’s what this program uses that applies to all (or almost all) Dart apps:

// This is a comment.

Use // to indicate that the rest of the line is a comment. Alternatively, use /* ... */. For details, see the section called “Comments”.

num

A type. Some of the other built-in types are String, int, and bool.

42

A number literal. Literals are a kind of compile-time constant.

print()

A handy way to display output.

'...' (or "...")

A string literal.

$variableName (or ${expression})

String interpolation: including a variable or expression’s string equivalent inside of a string literal. For more information, see the section called “Strings”.

main()

The special, required, top-level function where app execution starts. For more information, see the section called “The main() Function”.

var

A way to declare a variable without specifying its type.

Note

Our code follows the conventions in the Dart Style Guide. For example, we use two-space indentation.

Important Concepts

As you learn about the Dart language, keep these facts and concepts in mind:

  • Everything you can place in a variable is an object, and every object is an instance of a class. Even numbers, functions, and null are objects. All objects inherit from the Object class.

  • Specifying static types (such as num in the preceding example) clarifies your intent and enables static checking by tools, but it’s optional. (You might notice when you’re debugging your code that variables with no specified type get a special type: dynamic.)

  • Dart parses all your code before running it. You can provide tips to Dart—for example, by using types or compile-time constants—to catch errors or help your code run faster.

  • Dart supports top-level functions (such as main()), as well as functions tied to a class or object (static and instance methods, respectively). You can also create functions within functions (nested or local functions).

  • Similarly, Dart supports top-level variables, as well as variables tied to a class or object (static and instance variables). Instance variables are sometimes known as fields or properties.

  • Unlike Java, Dart doesn’t have the keywords public, protected, and private. If an identifier starts with an underscore (_), it’s private to its library. For details, see the section called “Libraries and Visibility”.

  • Identifiers can start with a letter or _, followed by any combination of those characters plus digits.

  • Sometimes it matters whether something is an expression or a statement, so we’ll be precise about those two words.

  • Dart tools can report two kinds of problems: warnings and errors. Warnings are just indications that your code might not work, but they don’t prevent your program from executing. Errors can be either compile-time or run-time. A compile-time error prevents the code from executing at all; a run-time error results in an exception being raised while the code executes.

  • Dart has two runtime modes: production and checked. Production is faster, but checked is helpful at development.

Keywords

Table 2.1, “Dart keywords” lists the words that the Dart language treats specially.

Table 2.1. Dart keywords

abstract *continueextendsimplements *part *throw
as *defaultfactory *import *rethrowtrue
assertdofalseinreturntry
breakdynamic *finalisset *typedef *
caseelsefinallylibrary *static *var
catchenumfornewsupervoid
classexport *get *nullswitchwhile
constexternal *ifoperator *thiswith


In the keyword table, words with an asterisk (*) are built-in identifiers. Although you should generally treat built-in identifiers like reserved words, the only real restriction is that you can't use a built-in identifier as the name of a class or type. Having built-in identifiers enables easier porting from JavaScript to Dart. For example, say some JavaScript code has a variable named factory; you don't have to rename it when you port the code to Dart. NEW for October update: added enum, rethrow. Dart reserved words: Section 16.1.1 of the spec lists reserved words; 12.31 has the identifier reference, which lists the built-in identifiers.

Runtime Modes

We recommend that you develop and debug in checked mode, and deploy to production mode.

Production mode is the default runtime mode of a Dart program, optimized for speed. Production mode ignores assert statements and static types.

Checked mode is a developer-friendly mode that helps you catch some type errors during runtime. For example, if you assign a non-number to a variable declared as a num, then checked mode throws an exception.

Variables

Here’s an example of creating a variable and assigning a value to it:

lang-dart
var name = 'Bob';

Variables are references. The variable called name contains a reference to a String object with a value of Bob.

Default Value

Uninitialized variables have an initial value of null. Even variables with numeric types are initially null, because numbers are objects.

lang-dart
int lineCount;
assert(lineCount == null); 
// Variables (even if they will be numbers) are initially null.

Note

The assert() call is ignored in production mode. In checked mode, assert(condition) throws an exception unless condition is true. For details, see the section called “Assert”.

Optional Types

You have the option of adding static types to your variable declarations:

lang-dart
String name = 'Bob';

Adding types is a way to clearly express your intent. Tools such as compilers and editors can use these types to help you, by providing code completion and early warnings for bugs and code completion.

Note

This chapter follows the style guide recommendation of using var, rather than type annotations, for local variables.

Final and Const

If you never intend to change a variable, use final or const, either instead of var or in addition to a type. A final variable can be set only once; a const variable is a compile-time constant.

A local, top-level, or class variable that’s declared as final is initialized the first time it’s used:

lang-dart
final name = 'Bob';   // Or: final String name = 'Bob';
// name = 'Alice';    // Uncommenting this results in an error

Note

Lazy initialization of final variables helps apps start up faster.

Use const for variables that you want to be compile-time constants. If the const variable is at the class level, mark it static const. (Instance variables can’t be const.) Where you declare the variable, set the value to a compile-time constant such as a literal, a const variable, or the result of an arithmetic operation on constant numbers:

lang-dart
const bar = 1000000;       // Unit of pressure (in dynes/cm2)
const atm = 1.01325 * bar; // Standard atmosphere

Built-in Types

The Dart language has special support for the following types:

  • numbers

  • strings

  • booleans

  • lists (also known as arrays)

  • maps

  • symbols

You can initialize an object of any of these special types using a literal. For example, 'this is a string' is a string literal, and true is a boolean literal.

Because every variable in Dart refers to an object—an instance of a class—you can usually use constructors to initialize variables. Some of the built-in types have their own constructors. For example, you can use the Map() constructor to create a map, using code such as new Map().

Numbers

Dart numbers come in two flavors:

int

Integer values, which generally should be in the range -253 to 253

double

64-bit (double-precision) floating-point numbers, as specified by the IEEE 754 standard

Both int and double are subtypes of num. The num type includes basic operators such as +, -, /, and *, as well as bitwise operators such as >>. The num type is also where you’ll find abs(), ceil(), and floor(), among other methods. If num and its subtypes don’t have what you’re looking for, the Math library might.

Warning

Integers outside of the -253 to 253 range currently behave differently in JavaScript produced from Dart code than they do when the same Dart code runs in the Dart VM. The reason is that Dart is specified to have arbitrary-precision integers, but JavaScript isn't. See issue 1533 for details.

Integers are numbers without a decimal point. Here are some examples of defining integer literals:

lang-dart
var x = 1;
var hex = 0xDEADBEEF;
var bigInt = 346534658346524376592384765923749587398457294759347029438709349347;

If a number includes a decimal, it is a double. Here are some examples of defining double literals:

lang-dart
var y = 1.1;
var exponents = 1.42e5;

Here’s how you turn a string into a number, or vice versa:

lang-dart
// String -> int
var one = int.parse('1');
assert(one == 1);

// String -> double
var onePointOne = double.parse('1.1');
assert(onePointOne == 1.1);

// int -> String
String oneAsString = 1.toString();
assert(oneAsString == '1');

// double -> String
String piAsString = 3.14159.toStringAsFixed(2);
assert(piAsString == '3.14');

The int type specifies the traditional bitwise shift (<<, >>), AND (&), and OR (|) operators. For example:

lang-dart
assert((3 << 1) == 6);  // 0011 << 1 == 0110
assert((3 >> 1) == 1);  // 0011 >> 1 == 0001
assert((3 | 4)  == 7);  // 0011 | 0100 == 0111

Strings

A Dart string is a sequence of UTF-16 code units. You can use either single or double quotes to create a string:

lang-dart
var s1 = 'Single quotes work well for string literals.';
var s2 = "Double quotes work just as well.";
var s3 = 'It\'s easy to escape the string delimiter.';
var s4 = "It's even easier to just use the other string delimiter.";

You can put the value of an expression inside a string by using ${expression}. If the expression is an identifier, you can skip the {}. To get the string corresponding to an object, Dart calls the object’s toString() method.

lang-dart
var s = 'string interpolation';

assert('Dart has $s, which is very handy.' ==
       'Dart has string interpolation, which is very handy.');
assert('That deserves all caps. ${s.toUpperCase()} is very handy!' ==
       'That deserves all caps. STRING INTERPOLATION is very handy!');

Note

The == operator tests whether two objects are equivalent. Two strings are equivalent if they contain the same sequence of code units.

You can concatenate strings using adjacent string literals or the + operator:

lang-dart
adjacent_string_literals.dart
var s1 = 'String ' 'concatenation'
         " works even over line breaks.";
assert(s1 == 'String concatenation works even over line breaks.');

var s2 = 'The addition operator '
         + 'works, as well.';
assert(s2 == 'The addition operator works, as well.');

Another way to create a multi-line string: use a triple quote with either single or double quotation marks:

lang-dart
var s1 = '''
You can create
multi-line strings like this one.
''';

var s2 = """This is also a
multi-line string.""";

You can create a raw string by prefixing it with r:

lang-dart
var s = r"In a raw string, even \n isn't special.";

You can use Unicode escapes inside of strings:

lang-dart
ch02/quoting.dart
print('Unicode escapes work: \u2665'); // Unicode escapes work: [heart]

For more information on using strings, see the section called “Strings and Regular Expressions”.

Booleans

To represent boolean values, Dart has a type named bool. Only two objects have type bool: the boolean literals, true and false.

When Dart expects a boolean value, only the value true is treated as true. All other values are treated as false. Unlike in JavaScript, values such as 1, "aString", and someObject are all treated as false.

For example, consider the following code, which is valid both as JavaScript and as Dart code:

lang-dart
var name = 'Bob';
if (name) {
  print('You have a name!'); // Prints in JavaScript, not in Dart.
}

If you run this code as JavaScript, it prints You have a name! because name is a non-null object. However, in Dart running in production mode, the above doesn’t print at all because name is converted to false (because name != true). In Dart running in checked mode, the above code throws an exception because the name variable is not a bool.

Here’s another example of code that behaves differently in JavaScript and Dart:

lang-dart
if (1) {
  print('JavaScript prints this line because it thinks 1 is true.');
} else {
  print('Dart in production mode prints this line.');
  // However, in checked mode, if (1) throws an exception.
}

Note

The previous two samples work only in production mode, not checked mode. In checked mode, an exception is thrown if a non-boolean is used when a boolean value is expected.

Dart’s treatment of booleans is designed to avoid the strange behaviors that can arise when many values can be treated as true. What this means for you is that, instead of using code like if (nonbooleanValue), you should instead explicitly check for values. For example:

lang-dart
// Check for an empty string.
var fullName = '';
assert(fullName.isEmpty);

// Check for zero.
var hitPoints = 0;
assert(hitPoints <= 0);

// Check for null.
var unicorn;
assert(unicorn == null);

// Check for NaN.
var iMeantToDoThis = 0/0;
assert(iMeantToDoThis.isNaN);

Lists

Perhaps the most common collection in nearly every programming language is the array, or ordered group of objects. In Dart, arrays are List objects, so we usually just call them lists.

Dart list literals look like JavaScript array literals. Here’s a simple Dart list:

lang-dart
var list = [1,2,3];

Lists use zero-based indexing, where 0 is the index of the first element and list.length - 1 is the index of the last element. You can get a list’s length and refer to list elements just as you would in JavaScript:

lang-dart
var list = [1,2,3];
assert(list.length == 3);
assert(list[1] == 2);

The List type has many handy methods for manipulating lists. For more information about lists, see the section called “Generics” and the section called “Collections”.

Maps

In general, a map is an object that associates keys and values. Both keys and values can be any type of object. Each key occurs only once, but you can use the same value multiple times. Dart support for maps is provided by map literals and the Map type.

Here are a couple of simple Dart maps, created using map literals:

lang-dart
var gifts = {
// Keys       Values
  'first'  : 'partridge',
  'second' : 'turtledoves',
  'fifth'  : 'golden rings'
};

var nobleGases = {
// Keys  Values
   2  : 'helium',
   10 : 'neon',
   18 : 'argon',
};

You can create the same objects using a Map constructor:

lang-dart
ch02/map_constructor.dart
var gifts = new Map();
gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';

var nobleGases = new Map();
nobleGases[2] = 'helium';
nobleGases[10] = 'neon';
nobleGases[18] = 'argon';

Add a new key-value pair to an existing map just as you would in JavaScript:

lang-dart
var gifts = { 'first': 'partridge' };
gifts['fourth'] = 'calling birds';    // Add a key-value pair

Retrieve a value from a map the same way you would in JavaScript:

lang-dart
var gifts = { 'first': 'partridge' };
assert(gifts['first'] == 'partridge');

If you look for a key that isn’t in a map, you get a null in return:

lang-dart
var gifts = { 'first': 'partridge' };
assert(gifts['fifth'] == null);

Use .length to get the number of key-value pairs in the map:

lang-dart
var gifts = { 'first': 'partridge' };
gifts['fourth'] = 'calling birds';
assert(gifts.length == 2);

For more information about maps, see the section called “Generics” and the section called “Maps”.

Symbols

A Symbol object represents an operator or identifier declared in a Dart program. You might never need to use symbols, but they're invaluable for APIs that refer to identifiers by name, because minification changes identifier names but not identifier symbols.

To get the symbol for an identifier, use a symbol literal, which is just # followed by the identifier:

lang-dart
#radix  // The symbol literal for an identifier named 'radix'.
#bar    // The symbol literal for an identifier named 'bar'.

For more information on symbols, see the section called “dart:mirrors - Reflection”.

Functions

Here’s an example of implementing a function:

lang-dart
void printNumber(num number) {
  print('The number is $number.');
}

Although the style guide recommends specifying the parameter and return types, you don’t have to:

lang-dart
printNumber(number) {          // Omitting types is OK.
  print('The number is $number.');
}

For functions that contain just one expression, you can use a shorthand syntax:

lang-dart
printNumber(number) => print('The number is $number.');

The => expr; syntax is a shorthand for { return expr;}. In the printNumber() function above, the expression is the call to the top-level print() function.

Note

Only an expression—not a statement—can appear between the arrow (=>) and the semicolon (;). For example, you can’t put an if statement there, but you can use a conditional (?:) expression.

You can use types with =>, although the convention is not to do so:

lang-dart
printNumber(num number) => print('The number is $number.'); // Types are OK.

Here’s an example of calling a function:

lang-dart
printNumber(123);

A function can have two types of parameters: required and optional. The required parameters are listed first, followed by any optional parameters.

Optional Parameters

Optional parameters can be either positional or named, but not both.

Both kinds of optional parameter can have default values. The default values must be compile-time constants such as literals. If no default value is provided, the value is null.

Optional named parameters

When calling a function, you can specify named parameters using paramName: value. For example:

lang-dart
enableFlags(bold: true, hidden: false);

When defining a function, use {param1, param2, …} to specify named parameters:

lang-dart
/// Sets the [bold] and [hidden] flags to the values you specify.
enableFlags({bool bold, bool hidden}) {
  // ...
}

Use a colon (:) to specify default values:

lang-dart
/**
 * Sets the [bold] and [hidden] flags to the values you specify,
 * defaulting to false.
 */
enableFlags({bool bold: false, bool hidden: false}) {
  // ...
}

enableFlags(bold: true); // bold will be true; hidden will be false.

Optional positional parameters

Wrapping a set of function parameters in [] marks them as optional positional parameters:

lang-dart
String say(String from, String msg, [String device]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  return result;
}

Here’s an example of calling this function without the optional parameter:

lang-dart
assert(say('Bob', 'Howdy') == 'Bob says Howdy');

And here’s an example of calling this function with the third parameter:

lang-dart
assert(say('Bob', 'Howdy', 'smoke signal') ==
  'Bob says Howdy with a smoke signal');

Use = to specify default values:

lang-dart
String say(String from, String msg,
  [String device='carrier pigeon', String mood]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  if (mood != null) {
    result = '$result (in a $mood mood)';
  }
  return result;
}

assert(say('Bob', 'Howdy') == 'Bob says Howdy with a carrier pigeon');

The main() Function

Every app must have a top-level main() function, which serves as the entrypoint to the app. The main() function returns void and has an optional List<String> parameter for arguments.

Here's an example of the main() function for a web app:

lang-dart
main() {
  querySelector("#sample_text_id")
    ..text = "Click me!"
    ..onClick.listen(reverseText);
}

Note

The .. operator in the preceding code is a cascade operator, which allows you to perform multiple operations on the members of a single object. You'll find out more in the section called “Classes”.

Here's an example of the main() function for a command-line app that takes arguments:

lang-dart
ch02/args.dart
// Run the app like this: dart args.dart 1 test
void main(List<String> arguments) {
  print(arguments);

  assert(arguments.length == 2); 
  assert(int.parse(arguments[0]) == 1);
  assert(arguments[1] == 'test'); 
}

You can use the args library to define and parse command-line arguments.

Functions as First-Class Objects

You can pass a function as a parameter to another function. For example:

lang-dart
printElement(element) {
  print(element);
}
  
var list = [1,2,3];
list.forEach(printElement); // Pass printElement as a parameter.

You can also assign a function to a variable, such as:

lang-dart
var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');

Lexical Scope

Dart is a lexically scoped language, which means that the scope of variables is determined statically, simply by the layout of the code. You can “follow the curly braces outwards” to see if a variable is in scope.

Here is an example of nested functions with variables at each scope level:

lang-dart
var topLevel = true;
main() {
    var insideMain = true;
    
    myFunction() {
      var insideFunction = true;
      
      nestedFunction() {
        var insideNestedFunction = true;
        assert(topLevel);
        assert(insideMain);
        assert(insideFunction);
        assert(insideNestedFunction);
      }
    }
}

Notice how nestedFunction() can use variables from every level, all the way up to the top level.

Lexical Closures

A closure is a function object that has access to variables in its lexical scope, even when the function is used outside of its original scope.

Functions can close over variables defined in surrounding scopes. In the following example, adder() captures the variable addBy. Wherever the returned function goes, it remembers addBy.

lang-dart
ch02/function_closure.dart
/// Returns a function that adds [addBy] to a number.
Function makeAdder(num addBy) {
  adder(num i) {
    return addBy + i;
  }
  return adder;
}

main() {
  var add2 = makeAdder(2); // Create a function that adds 2.
  var add4 = makeAdder(4); // Create a function that adds 4.

  assert(add2(3) == 5);
  assert(add4(3) == 7);
}

Testing Functions for Equality

Here's an example of testing top-level functions, static methods, and instance methods for equality:

lang-dart
ch02/function_equality_2.dart
foo() {}               // A top-level function

class SomeClass {
  static void bar() {} // A static method
  void baz() {}        // An instance method
}

main() {
  var x;
  
  // Comparing top-level functions.
  x = foo;
  assert(x == foo);
  
  // Comparing static methods.
  x = SomeClass.bar;
  assert(x == SomeClass.bar);
  
  // Comparing instance methods.
  var v = new SomeClass();
  var w = new SomeClass();
  var y = v;
  x = v.baz;
  
  assert(x == y.baz);
  assert(v.baz != w.baz);
}

Return Values

All functions return a value. If no return value is specified, the statement return null; is implicitly appended to the function body.

Operators

Dart defines the operators shown in Table 2.2, “Operators and their precedence”. You can override many of these operators, as described in the section called “Overridable Operators”.

Table 2.2. Operators and their precedence

DescriptionOperator
unary postfixexpr++    expr--    ()    []    .
unary prefix-expr    !expr    ~expr    ++expr    --expr   
multiplicative*    /    %    ~/
additive+    -
shift<<    >>
bitwise AND&
bitwise XOR^
bitwise OR|
relational and type test>=    >    <=    <    as    is    is!
equality==    !=   
logical AND&&
logical OR||
conditionalexpr1 ? expr2 : expr3
cascade..
assignment=    *=    /=    ~/=    %=    +=    -=    <<=    >>=    &=    ^=    |=   

When you use operators, you create expressions. Here are some examples of operator expressions:

lang-dart
a++
a + b
a = b
a == b
a? b: c
a is T

In Table 2.2, “Operators and their precedence”, each operator has higher precedence than the operators in the rows below it. For example, the multiplicative operator % has higher precedence than (and thus executes before) the equality operator ==, which has higher precedence than the logical AND operator &&. That precedence means that the following two lines of code execute the same way:

lang-dart
if ((n % i == 0) && (d % i == 0)) // Parens improve readability.
if (n % i == 0 && d % i == 0)     // Harder to read, but equivalent.

Warning

For operators that work on two operands, the leftmost operand determines which version of the operator is used. For example, if you have a Vector object and a Point object, aVector + aPoint uses the Vector version of +.

Arithmetic Operators

Dart supports the usual arithmetic operators, as shown in Table 2.3, “Arithmetic operators”.

Table 2.3. Arithmetic operators

OperatorMeaning
+Add
Subtract
-exprUnary minus, also known as negation (reverse the sign of the expression)
*Multiply
/Divide
~/Divide, returning an integer result
%Get the remainder of an integer division (modulo)

Example:

lang-dart
ch02/arithmetic_operators.dart
assert(2 + 3 == 5);
assert(2 - 3 == -1);
assert(2 * 3 == 6);
assert(5 / 2 == 2.5);   // Result is a double
assert(5 ~/ 2 == 2);    // Result is an integer
assert(5 % 2 == 1);     // Remainder

print('5/2 = ${5~/2} remainder ${5%2}'); // 5/2 = 2 remainder 1

Dart also supports both prefix and postfix increment and decrement operators.

Table 2.4. Increment and decrement operators

OperatorMeaning
++varvar = var + 1 (expression value is var + 1)
var++var = var + 1 (expression value is var)
--varvar = var – 1 (expression value is var – 1)
var--var = var – 1 (expression value is var)

Example:

lang-dart
var a, b;

a = 0;  
b = ++a;         // Increment a before b gets its value.
assert(a == b);  // 1 == 1  

a = 0;
b = a++;         // Increment a AFTER b gets its value.
assert(a != b);  // 1 != 0

a = 0;
b = --a;         // Decrement a before b gets its value.
assert(a == b);  // -1 == -1

a = 0;
b = a--;         // Decrement a AFTER b gets its value.
assert(a != b) ; // -1 != 0

Equality and Relational Operators

Table 2.5. Equality and relational operators

OperatorMeaning
==Equal; see discussion below
!=Not equal
>Greater than
<Less than
>=Greater than or equal to
<=Less than or equal to

To test whether two objects x and y represent the same thing, use the == operator. (In the rare case where you need to know whether two objects are the exact same object, use the identical() function instead.) Here’s how the == operator works:

  1. If x or y is null, return true if both are null, and false if only one is null.

  2. Return the result of the method invocation x.==(y). (That’s right, operators such as == are methods that are invoked on their first operand. You can even override many operators, including ==, as you’ll see in the section called “Overridable Operators”.)

Here’s an example of using each of the equality and relational operators:

lang-dart
ch02/op_equality.dart
assert(2 == 2);
assert(2 != 3);
assert(3 > 2);
assert(2 < 3);
assert(3 >= 3);
assert(2 <= 3);

Type Test Operators

The as, is, and is! operators are handy for checking types at runtime.

Table 2.6. Type test operators

OperatorMeaning
asTypecast
isTrue if the object has the specified type
is!False if the object has the specified type

The result of obj is T is true if obj implements the interface specified by T. For example, obj is Object is always true.

Use the as operator to cast an object to a particular type. In general, you should use it as a shorthand for an is test on an object following by an expression using that object. For example, consider the following code:

lang-dart
if (person is Person) {               // Type check
  person.firstName = 'Bob';
}

You can make the code shorter using the as operator:

lang-dart
(person as Person).firstName = 'Bob';

Note

The code isn’t equivalent. If person is null or not a Person, the first example (with is) does nothing; the second (with as) throws an exception.

Assignment Operators

As you’ve already seen, you assign values using the = operator. You can also use compound assignment operators such as +=, which combine an operation with an assignment.

Table 2.7. Assignment operators

=–=/=%=>>=^=
+=*=~/=<<=&=|=

Here’s how compound assignment operators work:

 Compound assignmentEquivalent expression
For an operator op:a op= ba = a op b
Example: a += ba = a + b

The following example uses both assignment and compound assignment operators:

lang-dart
var a = 2;           // Assign using =
a *= 3;              // Assign and multiply: a = a * 3
assert(a == 6);

Logical Operators

You can invert or combine boolean expressions using the logical operators.

Table 2.8. Logical operators

OperatorMeaning
!exprinverts the following expression (changes false to true, and vice versa)
||logical OR
&&logical AND

Here’s an example of using the logical operators:

lang-dart
if (!done && (col == 0 || col == 3)) {
  // ...Do something...
}

Bitwise and Shift Operators

You can manipulate the individual bits of numbers in Dart. Usually, you’d use these bitwise and shift operators with integers, as shown in Table 2.9, “Bitwise and shift operators”.

Table 2.9. Bitwise and shift operators

OperatorMeaning
&AND
|OR
^XOR
~exprUnary bitwise complement (0s become 1s; 1s become 0s)
<<Shift left
>>Shift right

Here’s an example of using bitwise and shift operators:

lang-dart
final value = 0x22;
final bitmask = 0x0f;

assert((value & bitmask)  == 0x02);  // AND
assert((value & ~bitmask) == 0x20);  // AND NOT
assert((value | bitmask)  == 0x2f);  // OR
assert((value ^ bitmask)  == 0x2d);  // XOR
assert((value << 4)       == 0x220); // Shift left
assert((value >> 4)       == 0x02);  // Shift right

Other Operators

A few operators remain, most of which you’ve already seen in other examples.

Table 2.10. Other operators

OperatorNameMeaning
()Function applicationRepresents a function call
[]List accessRefers to the value at the specified index in the list
expr1 ? expr2 : expr3ConditionalIf expr1 is true, executes expr2; otherwise, executes expr3
.Member accessRefers to a property of an expression; example: foo.bar selects property bar from expression foo
..CascadeAllows you to perform multiple operations on the members of a single object; described in the section called “Classes”

Control Flow Statements

You can control the flow of your Dart code using any of the following:

  • if and else

  • for loops

  • while and do-while loops

  • break and continue

  • switch and case

  • assert

You can also affect the control flow using try-catch and throw, as explained in the section called “Exceptions”.

If and Else

Dart supports if statements with optional else statements, as the next sample shows. Also see conditional expressions (?:), which are covered in the section called “Other Operators”.

lang-dart
if (isRaining()) {
  you.bringRainCoat();
} else if (isSnowing()) {
  you.wearJacket();
} else {
  car.putTopDown();
}

Remember, unlike JavaScript, Dart treats all values other than true as false. See the section called “Booleans” for more information.

For Loops

You can iterate with the standard for loop. For example:

lang-dart
ch02/flow_for_loops.dart
var message = new StringBuffer("Dart is fun");
for (var i = 0; i < 5; i++) {
  message.write('!');
}

Closures inside of Dart’s for loops capture the value of the index, avoiding a common pitfall found in JavaScript. For example, consider:

lang-dart
ch02/flow_for_loops.dart
var callbacks = [];
for (var i = 0; i < 2; i++) {
  callbacks.add(() => print(i));
}
callbacks.forEach((c) => c());

The output is 0 and then 1, as expected. In contrast, the example would print 2 and then 2 in JavaScript.

If the object that you are iterating over is an Iterable, you can use the forEach() method. Using forEach() is a good option if you don’t need to know the current iteration counter:

lang-dart
ch02/flow_for_loops.dart
candidates.forEach((candidate) => candidate.interview());

Iterable classes such as List and Set also support the for-in form of iteration, which is described in the section called “Iteration”:

lang-dart
ch02/flow_for_loops.dart
var collection = [0, 1, 2];
for (var x in collection) {
  print(x);
}

While and Do-While

A while loop evaluates the condition before the loop:

lang-dart
while(!isDone()) {
  doSomething();
}

A do-while loop evaluates the condition after the loop:

lang-dart
do {
  printLine();
} while (!atEndOfPage());

Break and Continue

Use break to stop looping:

lang-dart
while (true) {
  if (shutDownRequested()) break;
  processIncomingRequests();
}

Use continue to skip to the next loop iteration:

lang-dart
for (int i = 0; i < candidates.length; i++) {
  var candidate = candidates[i];
  if (candidate.yearsExperience < 5) {
    continue;
  }
  candidate.interview();
}

You might write that example differently if you’re using a Iterable such as a list or set:

lang-dart
candidates.where((c) => c.yearsExperience >= 5)
          .forEach((c) => c.interview());

Switch and Case

Switch statements in Dart compare integer, string, or compile-time constants using ==. The compared objects must all be instances of the same class (and not of any of its subtypes), and the class must not override ==.

Each non-empty case clause ends with a break statement, as a rule. Other valid ways to end a non-empty case clause are a continue, throw, or return statement.

Use a default clause to execute code when no case clause matches:

lang-dart
var command = 'OPEN';
switch (command) {
  case 'CLOSED':
    executeClosed();
    break;
  case 'PENDING':
    executePending();
    break;
  case 'APPROVED':
    executeApproved();
    break;
  case 'DENIED':
    executeDenied();
    break;
  case 'OPEN':
    executeOpen();
    break;
  default:
    executeUnknown();
}

The following example omits the break statement in the case clause, thus generating an error:

lang-dart
var command = 'OPEN';
switch (command) {
  case 'OPEN':
    executeOpen();
    // ERROR: Missing break causes an exception to be thrown!!

  case 'CLOSED':
    executeClosed();
    break;
}

However, Dart does support empty case clauses, allowing a form of fall-through:

lang-dart
var command = 'CLOSED';
switch (command) {
  case 'CLOSED':     // Empty case falls through.
  case 'NOW_CLOSED':
    // Runs for both CLOSED and NOW_CLOSED.
    executeNowClosed();
    break;
}

If you really want fall-through, you can use a continue statement and a label:

lang-dart
var command = 'CLOSED';
switch (command) {
  case 'CLOSED':
    executeClosed();
    continue nowClosed; // Continues executing at the nowClosed label.

nowClosed:
  case 'NOW_CLOSED':
    // Runs for both CLOSED and NOW_CLOSED.
    executeNowClosed();
    break;
}

A case clause can have local variables, which are visible only inside the scope of that clause.

Assert

Use an assert statement to disrupt normal execution if a boolean condition is false. You can find examples of assert statements throughout this tour. Here are some more:

lang-dart
assert(text != null);  // Make sure the variable has a non-null value.
assert(number < 100);  // Make sure the value is less than 100.
assert(urlString.startsWith('https')); // Make sure this is an HTTPS URL.

Note

Assert statements work only in checked mode. They have no effect in production mode.

Inside the parentheses after assert, you can put any expression that resolves to a boolean value or to a function. If the expression’s value or function’s return value is true, the assertion succeeds and execution continues. If it's false, the assertion fails and an exception (an AssertionError) is thrown.

Exceptions

Your Dart code can throw and catch exceptions. Exceptions are errors indicating that something unexpected happened. If the exception isn’t caught, the isolate that raised the exception is suspended, and typically the isolate and its program are terminated.

In contrast to Java, all of Dart’s exceptions are unchecked exceptions. Methods do not declare which exceptions they might throw, and you are not required to catch any exceptions.

Dart provides Exception and Error types, as well as numerous predefined subtypes. You can, of course, define your own exceptions. However, Dart programs can throw any non-null object—not just Exception and Error objects—as an exception.

Throw

Here’s an example of throwing, or raising, an exception:

lang-dart
throw new ExpectException('Value must be greater than zero');

You can also throw arbitrary objects:

lang-dart
throw 'Out of llamas!';

Because throwing an exception is an expression, you can throw exceptions in => statements, as well as anywhere else that allows expressions:

lang-dart
distanceTo(Point other) => throw new UnimplementedError();

Catch

Catching, or capturing, an exception stops the exception from propagating. Catching an exception gives you a chance to handle it:

lang-dart
try {
  breedMoreLlamas();
} on OutOfLlamasException {
  buyMoreLlamas();
}

To handle code that can throw more than one type of exception, you can specify multiple catch clauses. The first catch clause that matches the thrown object’s type handles the exception. If the catch clause does not specify a type, that clause can handle any type of thrown object:

lang-dart
try {
  breedMoreLlamas();
} on OutOfLlamasException {           // A specific exception
  buyMoreLlamas();
} on Exception catch(e) {             // Anything else that is an exception
  print('Unknown exception: $e');
} catch(e) {                          // No specified type, handles all
  print('Something really unknown: $e');
}

As the preceding code shows, you can use either on or catch or both. Use on when you need to specify the exception type. Use catch when your exception handler needs the exception object.

Finally

To ensure that some code runs whether or not an exception is thrown, use a finally clause. If no catch clause matches the exception, the exception is propagated after the finally clause runs:

lang-dart
try {
  breedMoreLlamas();
} finally {
  cleanLlamaStalls();  // Always clean up, even if an exception is thrown.
}

The finally clause runs after any matching catch clauses:

lang-dart
try {
  breedMoreLlamas();
} catch(e) {
  print('Error: $e');  // Handle the exception first.
} finally {
  cleanLlamaStalls();  // Then clean up.
}

Learn more by reading the section called “Exceptions”.

Classes

Dart is an object-oriented language with classes and mixin-based inheritance. Every object is an instance of a class, and all classes descend from Object. Mixin-based inheritance means that although every class (except for Object) has exactly one superclass, a class body can be reused in multiple class hierarchies.

To create an object, you can use the new keyword with a constructor for a class. Constructor names can be either ClassName or ClassName.identifier. For example:

lang-dart
var jsonData = JSON.decode('{"x":1, "y":2}');

var p1 = new Point(2,2);               // Create a Point using Point().
var p2 = new Point.fromJson(jsonData); // Create a Point using Point.fromJson().

Objects have members consisting of functions and data (methods and instance variables, respectively). When you call a method, you invoke it on an object: the method has access to that object’s functions and data.

Use a dot (.) to refer to an instance variable or method:

lang-dart
var p = new Point(2,2);

p.y = 3;             // Set the value of the instance variable y.
assert(p.y == 3);    // Get the value of y.

num distance = p.distanceTo(new Point(4,4)); // Invoke distanceTo() on p.

Use the cascade operator (..) when you want to perform a series of operations on the members of a single object:

lang-dart
querySelector('#button')
    ..text = 'Click to Confirm'                        // Get an object. Use its
    ..classes.add('important')                         // instance variables
    ..onClick.listen((e) => window.alert('Confirmed!')); // and methods.

Some classes provide constant constructors. To create a compile-time constant using a constant constructor, use const instead of new:

lang-dart
var p = const ImmutablePoint(2,2);

Constructing two identical compile-time constants results in a single, canonical instance:

lang-dart
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);

assert(identical(a,b)); // They are the same instance!

The following sections discuss how to implement classes.

Instance Variables

Here’s how you declare instance variables:

lang-dart
ch02/instance_variables.dart
class Point {
  num x;      // Declare an instance variable (x), initially null.
  num y;      // Declare y, initially null.
  num z = 0;  // Declare z, initially 0.
}

All uninitialized instance variables have the value null.

All instance variables generate an implicit getter method. Non-final instance variables also generate an implicit setter method. For details, see the section called “Getters and setters”.

lang-dart
ch02/instance_variables.dart
class Point {
  num x;
  num y;
}

main() {
  var point = new Point();
  point.x = 4;             // Use the setter method for x.
  assert(point.x == 4);    // Use the getter method for x.
  assert(point.y == null); // Values default to null.
}

If you initialize an instance variable where it is declared (instead of in a constructor or method), the value is set when the instance is created, which is before the constructor and its initializer list execute.

Constructors

Declare a constructor by creating a function with the same name as its class (plus, optionally, an additional identifier as described in the section called “Named constructors”). The most common form of constructor, the generative constructor, creates a new instance of a class:

lang-dart
ch02/constructor_long_way.dart
class Point {
  num x;
  num y;

  Point(num x, num y) {
    // There's a better way to do this, stay tuned.
    this.x = x;
    this.y = y;
  }
}

The this keyword refers to the current instance.

Note

Use this only when there is a name conflict. Otherwise, Dart style omits the this.

The pattern of assigning a constructor argument to an instance variable is so common, Dart has syntactic sugar to make it easy:

lang-dart
class Point {
  num x;
  num y;

  // Syntactic sugar for setting x and y before the constructor body runs.
  Point(this.x, this.y);
}

Default constructors

If you don’t declare a constructor, a default constructor is provided for you. The default constructor has no arguments and invokes the no-argument constructor in the superclass.

Constructors aren’t inherited

Subclasses don’t inherit constructors from their superclass. A subclass that declares no constructors has only the default (no argument, no name) constructor.

Named constructors

Use a named constructor to implement multiple constructors for a class or to provide extra clarity:

lang-dart
ch02/named_constructor.dart
class Point {
  num x;
  num y;

  Point(this.x, this.y);

  // Named constructor
  Point.fromJson(Map json) {
    x = json['x'];
    y = json['y'];
  }
}

Remember that constructors are not inherited, which means that a superclass’s named constructor is not inherited by a subclass. If you want a subclass to be created with a named constructor defined in the superclass, you must implement that constructor in the subclass.

Invoking a non-default superclass constructor

By default, a constructor in a subclass calls the superclass’s unnamed, no-argument constructor. If the superclass doesn’t have such a constructor, then you must manually call one of the constructors in the superclass. Specify the superclass constructor after a colon (:), just before the constructor body (if any).

lang-dart
ch02/op_as.dart
class Person {
  Person.fromJson(Map data) {
    print('in Person');
  }
}

class Employee extends Person {
  // Person does not have a default constructor;
  // you must call super.fromJson(data).
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }
}

main() {
  var emp = new Employee.fromJson({});

  // Prints:
  // in Person
  // in Employee
}

Because the arguments to the superclass constructor are evaluated before invoking the constructor, an argument can be an expression such as a function call:

lang-dart
ch02/method_then_constructor.dart
class Employee extends Person {
  ...
  Employee() : super.fromJson(findDefaultData());
}

Warning

Arguments to the superclass constructor do not have access to this. For example, arguments can call static methods but not instance methods.

Initializer list

Besides invoking a superclass constructor, you can also initialize instance variables before the constructor body runs. Separate initializers with commas.

lang-dart
ch02/initializer_list.dart
class Point {
  num x;
  num y;

  Point(this.x, this.y);

  // Initializer list sets instance variables before the constructor body runs.
  Point.fromJson(Map json) : x = json['x'], y = json['y'] {
    print('In Point.fromJson(): ($x, $y)');
  }
}

Warning

The right-hand side of an initializer does not have access to this.

Redirecting constructors

Sometimes a constructor’s only purpose is to redirect to another constructor in the same class. A redirecting constructor’s body is empty, with the constructor call appearing after a colon (:).

lang-dart
ch02/along_x_axis.dart
class Point {
  num x;
  num y;

  Point(this.x, this.y);                // The main constructor for this class.
  Point.alongXAxis(num x) : this(x, 0); // Delegates to the main constructor.
}

Constant constructors

If your class produces objects that never change, you can make these objects compile-time constants. To do this, define a const constructor and make sure that all instance variables are final.

lang-dart
ch02/immutable_point.dart
class ImmutablePoint {
  final num x;
  final num y;
  const ImmutablePoint(this.x, this.y);
  static final ImmutablePoint origin = const ImmutablePoint(0, 0);
}

Factory constructors

Use the factory keyword when implementing a constructor that doesn’t always create a new instance of its class. For example, a factory constructor might return an instance from a cache, or it might return an instance of a subtype.

The following example demonstrates a factory constructor returning objects from a cache:

lang-dart
ch02/factory_constructor.dart
class Logger {
  final String name;
  bool mute = false;

  // _cache is library-private, thanks to the _ in front of its name.
  static final Map<String, Logger> _cache = <String, Logger>{};
  
  factory Logger(String name) {
    if (_cache.containsKey(name)) {
      return _cache[name];
    } else {
      final logger = new Logger._internal(name);
      _cache[name] = logger;
      return logger;
    }
  }
  
  Logger._internal(this.name);
  
  void log(String msg) {
    if (!mute) {
      print(msg);
    }
  }
}

Note

Factory constructors have no access to this.

To invoke a factory constructor, you use the new keyword:

lang-dart
ch02/factory_constructor.dart
var logger = new Logger('UI');
logger.log('Button clicked');

Methods

Methods are functions that provide behavior for an object.

Instance methods

Instance methods on objects can access instance variables and this. The distanceTo() method in the following sample is an example of an instance method:

lang-dart
ch02/distance_to.dart
import 'dart:math';

class Point {
  num x;
  num y;
  Point(this.x, this.y);

  num distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return sqrt(dx * dx + dy * dy);
  }
}

Getters and setters

Getters and setters are special methods that provide read and write access to an object’s properties. Recall that each instance variable has an implicit getter, plus a setter if appropriate. You can create additional properties by implementing getters and setters, using the get and set keywords:

lang-dart
ch02/rectangle.dart
class Rectangle {
  num left;
  num top;
  num width;
  num height;

  Rectangle(this.left, this.top, this.width, this.height);

  // Define two calculated properties: right and bottom.
  num get right             => left + width;
      set right(num value)  => left = value - width;
  num get bottom            => top + height;
      set bottom(num value) => top = value - height;
}

main() {
  var rect = new Rectangle(3, 4, 20, 15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == -8);
}

With getters and setters, you can start with instance variables, later wrapping them with methods, all without changing client code.

Note

Operators such as increment (++) work in the expected way, whether or not a getter is explicitly defined. To avoid any unexpected side effects, the operator calls the getter exactly once, saving its value in a temporary variable.

Abstract methods

Instance, getter, and setter methods can be abstract, defining an interface but leaving its implementation up to other classes. To make a method abstract, use a semicolon (;) instead of a method body:

lang-dart
ch02/doer.dart
abstract class Doer {
  // ...Define instance variables and methods...
 
  void doSomething(); // Define an abstract method.
}

class EffectiveDoer extends Doer {
  void doSomething() {
    // ...Provide an implementation, so the method is not abstract here...
  }
}

Calling an abstract method results in a run-time error.

Also see the section called “Abstract Classes”.

Overridable Operators

You can override the operators shown in Table 2.11, “Operators that can be overridden”. For example, if you define a Vector class, you might define a + method to add two vectors.

Table 2.11. Operators that can be overridden

<+|[]
>/^[]=
<=~/&~
>=*<<==
%>> 

Here’s an example of a class that overrides the + and - operators:

lang-dart
ch02/vector.dart
class Vector {
  final int x;
  final int y;
  const Vector(this.x, this.y);

  Vector operator +(Vector v) { // Overrides + (a + b).
    return new Vector(x + v.x, y + v.y);
  }

  Vector operator -(Vector v) { // Overrides - (a - b).
    return new Vector(x - v.x, y - v.y);
  }
}

main() {
  final v = new Vector(2,3);
  final w = new Vector(2,2);

  assert(v.x == 2 && v.y == 3);         // v   == (2,3)
  assert((v+w).x == 4 && (v+w).y == 5); // v+w == (4,5)
  assert((v-w).x == 0 && (v-w).y == 1); // v-w == (0,1)
}

If you override ==, you should also override Object's hashCode getter. For an example of overriding == and hashCode, see the section called “Implementing map keys”.

For more information on overriding, in general, see the section called “Extending a Class”.

Abstract Classes

Use the abstract modifier to define an abstract class—a class that can’t be instantiated. Abstract classes are useful for defining interfaces, often with some implementation. If you want your abstract class to appear to be instantiable, define a factory constructor.

Abstract classes often have abstract methods. Here’s an example of declaring an abstract class that has an abstract method:

lang-dart
ch02/abstract.dart
// This class is declared abstract and thus can't be instantiated.
abstract class AbstractContainer {
  // ...Define constructors, fields, methods...

  void updateChildren(); // Abstract method.
}

The following class isn’t abstract, and thus can be instantiated even though it defines an abstract method:

lang-dart
ch02/abstract.dart
class SpecializedContainer extends AbstractContainer {
  // ...Define more constructors, fields, methods...

  void updateChildren() {
    // ...Implement updateChildren()...
  }
// Abstract method causes a warning but doesn't prevent instantiatation.
  void doSomething(); 
}

Implicit Interfaces

Every class implicitly defines an interface containing all the instance members of the class and of any interfaces it implements. If you want to create a class A that supports class B’s API without inheriting B’s implementation, class A should implement the B interface.

A class implements one or more interfaces by declaring them in an implements clause and then providing the APIs required by the interfaces. For example:

lang-dart
ch02/imposter.dart
// A person. The implicit interface contains greet().
class Person {
  final _name;          // In the interface, but visible only in this library,
  Person(this._name);   // Not in the interface, since this is a constructor.
  String greet(who) => 'Hello, $who. I am $_name.'; // In the interface.
}

// An implementation of the Person interface.
class Imposter implements Person {
  final _name = "";      // We have to define this, but we don't use it.
  String greet(who) => 'Hi $who. Do you know who I am?';
}

greetBob(Person person) => person.greet('bob');

main() {
  print(greetBob(new Person('kathy')));
  print(greetBob(new Imposter()));
}

Here’s an example of specifying that a class implements multiple interfaces:

lang-dart
ch02/point_interfaces.dart
class Point implements Comparable, Location {
  // ...
}

Extending a Class

Use extends to create a subclass, and super to refer to the superclass:

lang-dart
smart_tv.dart
class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
  ...
}

class SmartTelevision extends Television {
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
  ...
}

Subclasses can override instance methods, getters, and setters. Here’s an example of overriding the Object class’s noSuchMethod() method, which is called whenever code attempts to use a non-existent method or instance variable:

lang-dart
ch02/no_such_method.dart
class A {
  // Unless you override noSuchMethod, using a non-existent member
  // results in a NoSuchMethodError.
  void noSuchMethod(Invocation mirror) {
    print('You tried to use a non-existent member: ${mirror.memberName}');
  }
}

You can use the @override annotation to indicate that you are intentionally overriding a member:

lang-dart
ch02/ch02_meta/bin/ch02_override.dart
class A {
  @override
  void noSuchMethod(Invocation mirror) {
    // ...
  }
}

If you use noSuchMethod() to implement every possible getter, setter, and method for a class, then you can use the @proxy annotation to avoid warnings:

lang-dart
ch02/ch02_meta/bin/ch02_proxy.dart
@proxy
class A {
  void noSuchMethod(Invocation mirror) {
    // ...
  }
}

For more information on annotations, see the section called “Metadata”.

Adding Features to a Class: Mixins

Mixins are a way of reusing a class's code in multiple class hierarchies.

To use a mixin, use the with keyword followed by one or more mixin names. The following example shows two classes that use mixins:

lang-dart
ch02/mixins.dart
class Musician extends Performer with Musical {
  ...
}

class Maestro extends Person with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}

To implement a mixin, create a class that extends Object, declares no constructors, and has no calls to super. For example:

lang-dart
ch02/mixins.dart
abstract class Musical {
  bool canPlayPiano = false;
  bool canCompose = false;
  bool canConduct = false;
  
  void entertainMe() {
    if (canPlayPiano) {
      print('Playing piano');
    } else if (canConduct) {
      print('Waving hands');
    } else {
      print('Humming to self');
    }
  }
}

For more information, see the article Mixins in Dart.

Class Variables and Methods

Use the static keyword to implement class-wide variables and methods.

Static variables

Static variables (class variables) are useful for class-wide state and constants:

lang-dart
ch02/color.dart
class Color {
  static const RED = const Color('red'); // A constant static variable.
  final String name;                     // An instance variable.
  const Color(this.name);                // A constant constructor.
}

main() {
  assert(Color.RED.name == 'red');
}

Static variables aren’t initialized until they’re used.

Static methods

Static methods (class methods) do not operate on an instance, and thus do not have access to this. For example:

lang-dart
ch02/point.dart
import 'dart:math';

class Point {
  num x;
  num y;
  Point(this.x, this.y);

  static num distanceBetween(Point a, Point b) {
    var dx = a.x - b.x;
    var dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
  }
}

main() {
  var a = new Point(2, 2);
  var b = new Point(4, 4);
  var distance = Point.distanceBetween(a,b);
  assert(distance < 2.9 && distance > 2.8);
}

Note

Consider using top-level functions, instead of static methods, for common or widely used utilities and functionality.

You can use static methods as compile-time constants. For example, you can pass a static method as a parameter to a constant constructor.

Generics

If you look at the API documentation for the basic array type, List, you’ll see that the type is actually List<E>. The <...> notation marks List as a generic (or parameterized) type—a type that has formal type parameters. By convention, type variables have single-letter names, such as E, T, S, K, and V.

Why Use Generics?

Because types are optional in Dart, you never have to use generics. You might want to, though, for the same reason you might want to use other types in your code: types (generic or not) let you document and annotate your code, making your intent clearer.

For example, if you intend for a list to contain only strings, you can declare it as List<String> (read that as list of string). That way you, your fellow programmers, and your tools (such as Dart Editor and the Dart VM in checked mode) can detect that assigning a non-string to the list is probably a mistake. Here’s an example:

lang-dart
ch02/generics.dart
var names = new List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
// ...
names.add(42); // Fails in checked mode (succeeds in production mode).

Another reason for using generics is to reduce code duplication. Generics let you share a single interface and implementation between many types, while still taking advantage of checked mode and static analysis early warnings. For example, say you create an interface for caching an object:

lang-dart
abstract class ObjectCache {
  Object getByKey(String key);
  setByKey(String key, Object value);
}

You discover that you want a string-specific version of this interface, so you create another interface:

lang-dart
abstract class StringCache {
  String getByKey(String key);
  setByKey(String key, String value);
}

Later, you decide you want a number-specific version of this interface... You get the idea.

Generic types can save you the trouble of creating all these interfaces. Instead, you can create a single interface that takes a type parameter:

lang-dart
abstract class Cache<T> {
  T getByKey(String key);
  setByKey(String key, T value);
}

In this code, T is the stand-in type. It’s a placeholder that you can think of as a type that a developer will define later.

Using Collection Literals

List and map literals can be parameterized. Parameterized literals are just like the literals you’ve already seen, except that you add <type> (for lists) or <keyType, valueType> (for maps) before the opening bracket. You might use parameterized literals when you want type warnings in checked mode. Here is example of using typed literals:

lang-dart
var names = <String>['Seth', 'Kathy', 'Lars'];
var pages = <String, String>{
    'index.html':'Homepage',
    'robots.txt':'Hints for web robots',
    'humans.txt':'We are people, not machines' };

Using Parameterized Types with Constructors

To specify one or more types when using a constructor, put the types in angle brackets (<...>) just after the class name. For example:

lang-dart
var names = new List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
var nameSet = new Set<String>.from(names);

The following code creates a map that has integer keys and values of type View:

lang-dart
var views = new Map<int, View>();

Generic Collections and the Types they Contain

Dart generic types are reified, which means that they carry their type information around at runtime. For example, you can test the type of a collection, even in production mode:

lang-dart
var names = new List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true

However, the is expression checks the type of the collection only—not of the objects inside it. In production mode, a List<String> might have some non-string items in it. The solution is to either check each item’s type or wrap item-manipulation code in an exception handler (see the section called “Exceptions”).

Note

In contrast, generics in Java use erasure, which means that generic type parameters are removed at runtime. In Java, you can test whether an object is a List, but you can’t test whether it’s a List<String>.

For more information about generics, see Optional Types in Dart.

Libraries and Visibility

The import, part, and library directives can help you create a modular and shareable code base. Libraries not only provide APIs, but are a unit of privacy: identifiers that start with an underscore (_) are visible only inside the library. Every Dart app is a library, even if it doesn’t use a library directive.

Libraries can be distributed using packages. See the section called “pub: The Dart Package Manager” for information about pub, a package manager included in the SDK.

Using Libraries

Use import to specify how a namespace from one library is used in the scope of another library.

For example, Dart web apps generally use the dart:html library, which they can import like this:

lang-dart
ch02/libraries/using_libraries.dart
import 'dart:html';

The only required argument to import is a URI[1] specifying the library. For built-in libraries, the URI has the special dart: scheme. For other libraries, you can use a file system path or the package: scheme. The package: scheme specifies libraries provided by a package manager such as the pub tool. For example:

lang-dart
ch02/libraries/using_schemes.dart, mylib, utils
import 'dart:io';
import 'package:mylib/mylib.dart';
import 'package:utils/utils.dart';

Specifying a library prefix

If you import two libraries that have conflicting identifiers, then you can specify a prefix for one or both libraries. For example, if library1 and library2 both have an Element class, then you might have code like this:

lang-dart
ch02/libraries/library_prefix.dart, lib1, lib2
import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;
// ...
var element1 = new Element();      // Uses Element from lib1.
var element2 = new lib2.Element(); // Uses Element from lib2.

Importing only part of a library

If you want to use only part of a library, you can selectively import the library. For example:

lang-dart
ch02/libraries/library_partil.dart, lib1, lib2
import 'package:lib1/lib1.dart' show foo; // Import only foo.
import 'package:lib2/lib2.dart' hide foo; // Import all names EXCEPT foo.

Implementing Libraries

Use library to name a library, and part to specify additional files in the library.

Note

You don’t have to use library in an app (a file that has a top-level main() function), but doing so lets you implement the app in multiple files.

Declaring a library

Use library identifier to specify the name of the current library:

lang-dart
ch02/ballgame.dart
library ballgame;   // Declare that this is a library named ballgame.

import 'dart:html'; // This app uses the HTML library.
// ...Code goes here...

Associating a file with a library

To add an implementation file, put part fileUri in the file that has the library statement, where fileUri is the path to the implementation file. Then in the implementation file, put part of identifier, where identifier is the name of the library. The following example uses part and part of to implement a library in three files.

The first file, ballgame.dart, declares the ballgame library, imports other libraries it needs, and specifies that ball.dart and util.dart are parts of this library:

lang-dart
ch02/ballgame.dart
library ballgame;

import 'dart:html';
// ...Other imports go here...

part 'ball.dart';
part 'util.dart';

// ...Code might go here...

The second file, ball.dart, implements part of the ballgame library:

lang-dart
ch02/ball.dart
part of ballgame;

// ...Code goes here...

The third file, util.dart, implements the rest of the ballgame library:

lang-dart
ch02/util.dart
part of ballgame;

// ...Code goes here...

Re-exporting libraries

You can combine or repackage libraries by re-exporting part or all of them. For example, you might have a huge library that you implement as a set of smaller libraries. Or you might create a library that provides a subset of methods from another library.

lang-dart
ch02/french.dart, togo.dart, french_togo.dart
// In french.dart:
library french;
hello() => print('Bonjour!');
goodbye() => print('Au Revoir!');

// In togo.dart:
library togo;
import 'french.dart';
export 'french.dart' show hello;

// In another .dart file:
import 'togo.dart';

void main() {
  hello();   //print bonjour
  goodbye(); //FAIL
}

Isolates

Modern web browsers, even on mobile platforms, run on multi-core CPUs. To take advantage of all those cores, developers traditionally use shared-memory threads running concurrently. However, shared-state concurrency is error prone and can lead to complicated code.

Instead of threads, all Dart code runs inside of isolates. Each isolate has its own memory heap, ensuring that no isolate’s state is accessible from any other isolate.

Typedefs

In Dart, functions are objects, just like strings and numbers are objects. A typedef, or function-type alias, gives a function type a name that you can use when declaring fields and return types. A typedef retains type information when a function type is assigned to a variable.

Consider the following code, which does not use a typedef:

lang-dart
ch02/sorted_collection.dart
class SortedCollection {
  Function compare;

  SortedCollection(int f(Object a, Object b)) {
    compare = f;
  }
}

int sort(Object a, Object b) => ... ; // Initial, broken implementation.

main() {
  SortedCollection collection = new SortedCollection(sort);

  // All we know is that compare is a function, but what type of function?
  assert(collection.compare is Function);
}

Type information is lost when assigning f to compare. The type of f is (Object, Object) int (where means returns), yet the type of compare is Function. If we change the code to use explicit names and retain type information, both developers and tools can use that information.

lang-dart
typedef int Compare(Object a, Object b);

class SortedCollection {
  Compare compare;

  SortedCollection(this.compare);
}

int sort(Object a, Object b) => ... ; // Initial, broken implementation.

main() {
  SortedCollection collection = new SortedCollection(sort);
  assert(collection.compare is Function);
  assert(collection.compare is Compare);
}

Note

Currently, typedefs are restricted to function types. We expect this to change.

Because typedefs are simply aliases, they offer a way to check the type of any function. For example:

lang-dart
typedef int Compare(int a, int b);

int sort(int a, int b) => a - b;

main() {
  assert(sort is Compare);  // True!
}

Metadata

Use metadata to give additional information about your code. A metadata annotation begins with the character @, followed by either a reference to a compile-time constant (such as deprecated) or a call to a constant constructor.

Three annotations are available to all Dart code: @deprecated, @override, and @proxy. For examples of using @override and @proxy, see the section called “Extending a Class”. Here’s an example of using the @deprecated annotation:

lang-dart
ch02/ch02_meta/bin/ch02_meta.dart
class Television {
  /// _Deprecated: Use [turnOn] instead._
  @deprecated      // Metadata; makes Dart Editor warn about using activate().
  void activate() {
    turnOn();
  }

  /// Turns the TV's power on.
  void turnOn() {
    print('on!');
  }
}

You can define your own metadata annotations. Here’s an example of defining a @todo annotation that takes two arguments:

lang-dart
ch02/ch02_meta_create/todo.dart
library todo;

class todo {
  final String who;
  final String what;
  
  const todo(this.who, this.what);
}

And here’s an example of using that @todo annotation:

lang-dart
ch02/ch02_meta_create/metadata_user.dart
import 'todo.dart';

@todo('seth', 'make this do something')
void doSomething() {
  print('do something');
}

Metadata can appear before a library, class, typedef, type parameter, constructor, factory, function, field, parameter, or variable declaration and before an import or export directive. If issue #6614 is fixed by the time you read this, you can retrieve metadata at runtime using reflection.

Comments

Dart supports single-line comments, multi-line comments, and documentation comments.

Single-Line Comments

A single-line comment begins with //. Everything between // and the end of line is ignored by the Dart compiler.

lang-dart
main() {
  // TODO: refactor into an AbstractLlamaGreetingFactory?
  print('Welcome to my Llama farm!');
}

Multi-Line Comments

A multi-line comment begins with /* and ends with */. Everything between /* and */ is ignored by the Dart compiler (unless the comment is a documentation comment; see the next section). Multi-line comments can nest.

lang-dart
ch02/multi_line_comments.dart
main() {
  /*
   * This is a lot of work. Consider raising chickens.

  Llama larry = new Llama();
  larry.feed();
  larry.exercise();
  larry.clean();
   */
}

Documentation Comments

Documentation comments are multi-line or single-line comments that begin with /** or ///. Using /// on consecutive lines has the same effect as a multi-line doc comment.

Inside a documentation comment, the Dart compiler ignores all text unless it is enclosed in brackets. Using brackets, you can refer to classes, methods, fields, top-level variables, functions, and parameters. The names in brackets are resolved in the lexical scope of the documented program element.

Here is an example of documentation comments with references to other classes and arguments:

lang-dart
ch02/doc_comments.dart
/**
 * A domesticated South American camelid (Lama glama).
 * 
 * Andean cultures have used llamas as meat and pack animals
 * since pre-Hispanic times.
 */
class Llama {
  String name;

  /**
   * Feeds your llama [Food].
   * 
   * The typical llama eats one bale of hay per week.
   */
  void feed(Food food) {
    // ...
  }

  /// Exercises your llama with an [activity] for
  /// [timeLimit] minutes.
  void exercise(Activity activity, int timeLimit) {
    // ...
  }
}

In the generated documentation, [Food] becomes a link to the API docs for the Food class.

To parse Dart code and generate HTML documentation, you can use the SDK’s documentation generation tool. For an example of generated documentation, see the Dart API documentation. For advice on how to structure your comments, see Guidelines for Dart Doc Comments.

Summary

This chapter summarized the commonly used features in the Dart language. More features are being implemented, but we expect that they won’t break existing code. For more information, see the Dart Language Specification and articles such as Idiomatic Dart.



[1] URI stands for uniform resource identifier. URLs (uniform resource locators) are a common kind of URI.