Built-in types and their methods

Like Ruby, Dart is a purely object-oriented (OO) language, so every variable in Dart points to an object and there are no primitive types like in Java or C#. Every variable is an instance of a class (that inherits from the Object base class) and has a type and, when uninitialized, has the null value. However, for easy use, Dart has built-in types for the numbers, Booleans, and Strings defined in dart:core that look and behave like primitive types; that is, they can be made with literal values and have the basic operations that you would expect (to make it clear, we will use full typing in builtin_types.dart, but we could have used var as well).

A String (notice the capital) is a sequence of Unicode (UTF-16) characters, for example:

String country = "Egypt";
String chineseForWorld = '世界';

They can be indicated by paired ' or " (use "" when the string contains ' and vice versa). Adjacent string literals are concatenated. If you need multiline strings, use triple quotes ''' or """ (handy for defining chunks of HTML!).

Escape characters are not interpreted when the string is prefixed by r, a so-called raw string, which is invaluable while defining regular expressions. The empty string '' or "" is not the same as null. The following are all legal strings:

  String q = "What's up?";
  String s1 = 'abc'
            "def";
  print(s1); // abcdef
  String multiLine = '''
    <h1> Beautiful page </h1>
    <div class="start"> This is a story about the landing 
       on the moon </div>
    <hr>
  ''';
  print(multiLine);
  String rawStr = r"Why don't you \t learn Dart!"; 
  // output: Why don't you \t learn Dart!
  print(rawStr);
  var emptyStr = ''; // empty string

The num (number) types are int (integer) and double, which are both subtypes of num:

int n = 42;
double pi = 3.14;

Integers can use a hexadecimal notation preceding with 0x, as shown in the following code:

int hex = 0xDEADBEEF;

They can be of arbitrary precision, as shown in the following code:

int hugePrimeNumber = 4776913109852041418248056622882488319;

Note

You cannot use this feature if you rely on the compilation to JS, because here, we are restricted to JS integers!

Doubles are of a 64-bit precision and can use scientific notation:

double d1 = 12345e-4;

The num type has the usual abs(), ceil(), and floor() methods as well as round() for rounding. Use either int or num, but double only in the specific case when you need a decimal number that cannot be an integer.

Booleans can only have a true or false value:

bool selected = false;

In contrast to JavaScript, where any variable can be evaluated as true or false, Dart does not permit these strange behaviors in the checked mode; in the production mode, every value different from true is treated as false.

Conversions

To use numbers as strings, use the toString() method and to convert String into int, use int.parse():

String lucky = 7.toString();
int seven = int.parse('7');

Likewise, converting String into double is done with double.parse():

double pi2 = double.parse('3.1415');

If you want to retain only a certain amount of decimal numbers from double, use toStringAsFixed():

String pi2Str = pi2.toStringAsFixed(3);//  3.142 (notice rounding occurs)

To convert between the num types, use toDouble() for int to double and toInt() for double to int (truncation occurs!).

Operators

All the operators in Dart follow the normal priority rules, when in doubt or for clarity, use () around the expressions that must be executed first.

We have our familiar number operators (+, -, *, /, and %) in Dart, and assignments with these can be shortened as +=. Use ~/ to get an integer result from a division. Likewise, we have prefixed and postfixed ++ and -- to add or subtract 1 to or from a number, and <, <=, >, and >= to check the order of the numbers or strings.

Tip

Strings a and b can be concatenated with + as a + b, but string interpolation such as '$a $b' executes faster, so we will prefer this.

Numbers have also bitwise- and shift-operators to manipulate individual bits.

To see if the two variables have the same content, use ==. Or, to check for different content, use !=. These expressions result in Boolean values, such as b1 and b2 in this snippet (brackets are only used for clarity):

var i = 100;
var j = 1000;
var b1 = (i == j);
var b2 = (i!= j);
print('$b1'); // false
print('$b2'); // true

For numbers and strings, == is true when both the variables have the same value.

Note

The == operator can be redefined for any type; generally, it will check whether both the arguments have the same value. Use the identical(a,b) function to check whether variables a and b refer to the same object.

For strings, both hold true; the same string is only one object in memory, and if the string variable gets a new value, it references another address in memory. Strings are immutable:

var s = "strings are immutable";
var t = "strings are immutable";
print(s == t); // true, they contain the same characters
print(identical(s, t)); // true, they are the
 // same object in memory

Boolean values or expressions can be combined with an AND operator (&&) or an OR operator (||), or negated with a NOT operator (!).

Because we will be working a lot with objects and types in the Dart code, it is important to be able to test if an object is or is! (not) of a certain type (class):

var b3 = (7 is num); // () are not necessary
print('$b3');        // true
var b4 = (7 is! double);
print('$b4');        // true, it's an int

A very useful built-in function, which can be used for micro unit testing, is assert; its parameter is a Boolean expression. When this is true, nothing happens at runtime; but when it is false, the program stops with AssertionError. You can sprinkle them around in your code to test certain conditions that must hold; in the production mode, the assert statements are ignored. So, for the last example, we could have written:

assert(b4 == true) or shorter assert(b4)

We will use these throughout the example code, but will not print them in the text for brevity.

The [] indexing operator is used to obtain an element from a collection (a group of variables) at a certain index; the first element has a 0 index.

To convert or cast a v variable to a certain T type, use the as operator: v as T.

If v indeed is of this type, all is well and you will be able to access all of T's methods, but if this fails, an error will be generated.

Some useful String methods

Strings are all-pervasive and Dart provides handy methods to work with them. For details, refer to the documentation at http://api.dartlang.org/docs/releases/latest/dart_core/String.html.

We will show some examples in string_methods.dart:

  • You can test that the owner of a bank account (a string) is not filled in with owner.isEmpty, which returns a Boolean value:
    assert("".isEmpty);
  • length() returns the number of UTF16 characters:
    assert('Google'.length == 6);
  • Use trim() to remove the leading and trailing whitespace:
    assert('\thello  '.trim() == 'hello');
  • Use startswith(), endswith(), and contains() to detect the presence of subwords:
    var fullName = 'Larry Page';
    assert(fullName.startsWith('La'));
    assert(fullName.endsWith('age'));
    assert(fullName.contains('y P'));
  • Use replaceAll() to replace a substring, notice that the original string was not changed (strings are immutable!):
    var composer = 'Johann Sebastian Bach';
    var s = composer.replaceAll('a', '-');
    print(s); // Joh-nn Seb-sti-n B-ch
    assert(s != composer); // composer didn't change
  • Use the [] operator to get the character at index i in the string:
    var lang = "Dart";
    assert(lang[0] == "D");
  • Find the location of a substring inside a string with indexOf():
    assert(lang.indexOf("ar") == 1);
  • Extract a part of a string with substring():
    assert("20000 rabbits".substring(9, 13) == 'bits');

While printing any object, the toString() method, which returns a string, is automatically called. If no particular version of this method was provided, the toString() method from the Object class is called, which prints the type of the object, as shown in the following code:

print('$ba');       //    produces Instance of 'BankAccount'

Tip

If you need a readable representation of an object, give its class a toString() method.

In banking_v2.dart, we provide the following method:

String toString() => 'Bank account from $owner with number $number and balance $balance';

Now, print('$ba'); produces the following output:

Bank account from John Gates with number 075-0623456-72and balance 1000.0

If you need many operations to build your strings, instead of creating new Strings at each operation and thus using more memory, consider using a StringBuffer object for better efficiency. A StringBuffer object like sb in the following code doesn't generate a new String object until toString() is called. An example is given in the following code:

var sb = new StringBuffer();
sb.write("Use a StringBuffer ");
sb.writeAll(["for ", "efficient ", "string ", "creation "]);
sb.write("if you are ");
sb.write("building lots of strings.");
var fullString = sb.toString();
print('$fullString');
sb.clear();  // sb is empty again
assert(sb.toString() == '');

Dates and times

Almost every app needs time information, so how can we do it in Dart? The dart:core package has a DateTime class for this. In our banking app, we could add the dateCreated and dateModified attributes to our BankAccount class. In the constructor, dateCreated would be initialized to the moment at which the account is created; in our deposit and withdraw methods, we could update dateModified. This is shown in the following code (refer to banking_v2.dart):

class BankAccount {
  String owner, number;
  double balance;
  DateTime dateCreated, dateModified;

  BankAccount(this.owner, this.number, this.balance)  {
    dateCreated = new DateTime.now();
  }

  deposit(double amount) {
    balance += amount;
 dateModified = new DateTime.now();
  }
  // other code
}

We can print this out with the following command:

print('Bank account created at: ${ba.dateCreated}');

The output produced is as follows:

Bank account created at: 2013-02-10  10:42:45.387

The DateTime.parse(dateString) method produces a DateTime object from a string in one of the suitable formats: 20130227 13:27:00 or 2010-01-17. All the weekdays and month names are defined as const int, such as MON and JAN. You can extract all the date parts as an int with methods such as second, day, month, and year, as shown in the following code:

ba.dateModified.weekday

A time span is represented by a Duration object, difference() gives the duration between two DateTime objects, and you can add and subtract a duration from DateTime.

List

This is the basic collection type to make an ordered group of objects; it can be of a fixed size (called an array in other languages) or it can grow dynamically. Again length returns the number of elements in the List; the last element has a length – 1 index. An empty list with length equal to 0 and an isEmpty property equal to true can be created in two ways: literal or with a constructor (refer to lists.dart):

var empty = [];
var empty2 = new List(); // equivalent
assert(empty.isEmpty && empty2.isEmpty && empty.length == 0);

We can either define and populate a List with a literal by using [] like in the following code:

var langs = ["Java","Python","Ruby", "Dart"];

Or, we can define a List object with a constructor and an add() method:

var langs2 = new List();
langs2.add("C");
langs2.add("C#");
langs2.add("D");
print(langs2); // [C, C#, D]

A read-only List with constant elements resulting in better performance can be defined as shown in the following code:

var readOnlyList = const ["Java","Python","Ruby", "Dart"];

The [] operator can be used to fetch and set list elements:

var langBest = langs[3];
assert(langBest=="Dart");
langs2[2] = "JavaScript";

But using an index greater than or equal to the length of the List, provokes RangeError in runtime (with no compile-time check!):

langs[4] = "F#";  // RangeError !

To check whether a List contains a certain item, use the method with the name:

print('${langs.contains("Dart")}'); // true

When you know the type of the List elements, the List itself can be typed, for example, langs and langs2 are both of the List<String> type.

A string can be split over a certain character or pattern (which could be a space " " or even ""), producing a List<String> named parts in the following code, which can then be further analyzed:

var number = "075-0623456-72";
var parts = number.split('-');
print('$parts'); // produces [075, 0623456, 72]

In simple scenarios, data records are written line after line in text files, each line containing the data of one object. In each line, the data fields are separated by a certain character, such as ";". We could read in and split each line of the file, and obtain a list of fields for each object to be shown on a screen or processed further. Conversely, a List can be joined by concatenating all its elements in one String (here, with a separator '-'):

var str = parts.join('-');
assert(number==str);

A List with n elements is used mostly to support an efficient search of the whole List or a large number of the List's elements. The time it takes to search a List grows linearly with n; it is of the o (n) order.

To summarize it, a List is an ordered collection of the items that can be retrieved or changed by index (0-based, working via index is fast), and that can contain duplicates. You can find more useful functions in the API, but we will be coming back to the List again in Chapter 3, Structuring Code with Classes and Libraries, in The collection hierarchy and its functional nature section. (For API docs, see the documentation at http://api.dartlang.org/docs/releases/latest/dart_core/List.html).

Maps

Another very useful and built-in type is Map, basically a dictionary of (key:value) pairs, where the value is associated with the key. The number of pairs is the length of the Map constructor. Keys must be unique, they may not be null and the lookup of the value from the key is fast; mind, however, that the order of the pairs is not guaranteed! Similar to List, a Map constructor can be created literally with {} as shown in the following code:

Map webLinks = {  'Dart': 'http://www.dartlang.org/','HTML5': 'http://www.html5rocks.com/'};

The keys must be of the string type for a literal Map. Or, it can be created with a constructor (refer to maps.dart):

Map webLinks2 = new Map();
webLinks2['Dart'] = 'http://www.dartlang.org/';     (1)
webLinks2['HTML5'] = 'http://www.html5rocks.com/';

The empty Map created with var map = {} or var map = new Map() has length as 0; the length of Map is not fixed. You can fetch the value corresponding to a certain key with:

var link = webLinks2['Dart']; // 'http://www.dartlang.org/'

If the key is not in the Map constructor, you will get null (it is not an error):

var link2 = webLinks2['C'];  // null

To check whether your Map contains a certain key, use the containsKey() method:

if (webLinks2.containsKey('C'))
  print("The map webLinks2 contains key 'C");
else
  print("The map webLinks2 does not contain key 'C'");
// prints: The map webLinks2 does not contain key 'C'

To obtain a List of the keys or values, use the methods with the same name:

var keys = webLinks2.keys.toList();
print('$keys'); // [Dart, HTML5, ASP.NET]
// getting the values:
var values = webLinks2.values.toList();
print('$values');
// printed output:
// [http://www.learningdart.org/, http://www.html5rocks.com/,
// http://www.asp.net/]

Setting a value is done with the syntax shown in line (1); this applies both to inserting a new key-value pair in the map or changing the value for an existing key:

webLinks2['Dart'] = 'http://www.learningdart.org/';  // change
webLinks2['ASP.NET'] = 'http://www.asp.net/';  // new key

A very handy method is putIfAbsent, which makes your code a lot cleaner. It takes two parameters: a key and a function that returns a value. The method tests whether the key already exists; if not, the function is evaluated and the value returned is assigned to the key (for example, we use a very simple function that directly returns a value, but this could be a calculation or a database lookup operation):

webLinks2.putIfAbsent('F#', () => 'www.fsharp.net');
assert(webLinks2['F#']=="www.fsharp.net");

Again, for performance reasons, use the const maps when the keys and values are literals or constants:

var cities =  const {'1':'London','2':'Tokyo','3':'Madrid'};

A Map constructor can also be explicitly typed, for example, Map with integer keys and String values:

Map<int, String>

A Map constructor with n elements is used mostly to support an efficient direct access to a single element of the map based on its key. This will always execute in the same time regardless of the size of the input dataset; the algorithm is of the order O(1).

(For the API docs for Map, see the documentation at http://api.dartlang.org/docs/releases/latest/dart_core/Map.html).