Book Image

DART Essentials

Book Image

DART Essentials

Overview of this book

Table of Contents (16 chapters)
Dart Essentials
Credits
About the Author
About the Reviewers
www.PacktPub.com
Preface
Index

The Dart language tour


We've already gone through many concepts of the Dart language when explaining the code, but there are still some aspects that we should look at in greater detail.

Note

The Dart team maintains an official style guide for Dart at https://www.dartlang.org/articles/style-guide.

The static checker

Dart VM can run in production or checked mode. The checked mode performs runtime type checking and runs all assert(condition) checks. Asserts are a way to quickly test whether a condition is true, and if it's not, then stop the script execution. Runtime type checks can, for example, detect when you declare a variable int and then try to assign it a String object.

This is useful when developing or testing your app, but it introduces some overhead, and for this reason, Dart VM runs in production mode by default and simply ignores all these checks.

Dart Editor launches Dartium with enabled checked mode, but you can switch to production mode in the top menu window by navigating to Run | Manage Launches and by unchecking Run in checked mode for the configuration that runs in Dartium. For the standalone Dart VM, you can enable the checked mode with the -c parameter (for example, in command line, dart -c main-cli.dart).

Variables

We've already seen how to declare variables and we also saw that all types in Dart are optional. Apart from this, we can define variables as const or final.

The final keyword lets you assign a value to a variable only once. This behavior is slightly different when assigning objects because final only locks a variable's values and when you're assigning objects, you're working with pointers (their memory addresses). In other words, you're assigning a memory address to a variable and it doesn't care what the internal state of the object is. It can change in runtime. The final keyword only prevents you from assigning a different value to the same variable. For example, you can declare an instance of List<String> as final and add items dynamically during the app's lifetime.

The const keyword lets you assign a value to a variable at compile time. For this reason, you can't make a variable const if its value is dependent on an expression that isn't determinable at compile time. The same restrictions are applied when instantiating objects where all their properties have to be compile time determinable too.

Note

All uninitialized variables have the null value.

Built-in types

Everything in Dart is an object; however, some types are treated in a special way:

  • Numbers: Built-in int and double types are both subclasses of the num abstract class. All integers and floating point numbers are stored as int or double, respectively.

  • Strings: All strings in Dart are stored as String objects. Both single and double quotes to enclose a string are allowed. Classes can implement the toString()method, which should return a textual representation of the inner state of an object.

  • Booleans: These are true/false values. Note that in JavaScript, a value is considered to be true if it's a nonzero length string, nonempty array, any value different from 0, and so on. But in Dart, only the Boolean value true is treated as true, nothing else.

  • Lists: All lists (or arrays) are instances of List class. You can use short notations to create lists just like in JavaScript with this:

    List<T> list = [1, 2, 3];
  • Maps: Key-value storage in Dart is represented by the Map class. You can use short notation with this as well:

    Map<K,V> map = {
      'key1' => 'value',
      'key2' => 123
    };

You can get a value associated to a key with a square bracket notation, such as map['key']. In contrast to JavaScript, the Map class in Dart has a public property, length, which represents the total number of key-value pairs in the map.

Functions and parameters

Defining functions is nothing new, and we've already seen this in the preceding sections. Dart extends function definitions with optional positional and named parameters, both well known from Python.

After the required parameters, you can specify any number of optional parameters.

Named parameters are mostly useful when you have a function with many parameters, and in most use cases, you need to use only a few of them. You can define named parameters as a map with curly brackets, {}:

void myFunc(required, {bool opt1: true, String opt2: "hello"}) {
  /* ... */
}

Another method is to use optional positional parameters. You can define positional parameters with square brackets, []:

void myFunc2(required, [bool named1, String named2]) {
  /* ... */
}

One function definition can't combine both named and positional optional parameters. Some examples of calling both types could be:

myFunc(true, opt2: "Hello, World!");
myFunc2(false, true, "I'm Dart");

Class properties

When defining classes, there are no public, protected or private keywords like in PHP or Java. Instead, every property that starts with _ is private. Everything else is public. Dart also generates default getters and setters for each property, but you can override them or create your custom properties:

Class Square {
  num height;
  num width;
  
  num get size => height * width;
  set size(int value) {
    width = height = sqrt(value);
  }
}

You can later use size just like any other public property:

var cls = new Square();
cls.size = 49;

This updates both width and height.

Class inheritance and abstract classes

Dart is an object-oriented language with a mixin-based inheritance model. This means that every class has exactly one superclass but can implement multiple classes as interfaces and use multiple class bodies (mixins). Every class is at least a subclass of the Object class.

Abstract classes can't be instantiated directly and often contain abstract methods.

Any class in Dart can be treated as an interface for another class, which might be a little unusual:

abstract class MyAbsClass {
  String hello();
}
class MyInterface {
  String hello2() => "hello!";
}
class MyInterface2 {
  String anotherHello() => "hello!";
}

class MyClass extends MyAbsClass implements MyInterface, MyInterface2 {
  String hello() => "Ahoy";
  String hello2() {
    /* ... */
  }
  String anotherHello() {
    /* ... */
  }
}

Note that the hello()abstract method in MyAbsClass doesn't need a keyword in front of it. It just doesn't have any body. The MyClass class implements two classes as its interfaces and automatically takes all their methods as abstract and requires you to implement them. This isn't the same as inheritance because it completely ignores methods' bodies and expects you to write them by yourself. There's also keyword super that refers to the superclass.

In later chapters, we'll explain and use mixins as well.

Constructors

There are two types of constructors, generative and named. Generative constructors are the same constructors as in any other language. Their name equals the class name:

class MyClassWithConst {
  num height;
  num width;
  
  MyClassWithConst(num w, num h) {
    width = w;
    height = h;
  }
}

Assigning default values to object properties is very common, so there's a shorter way to write the preceding constructor with just the following:

MyClassWithConst(this.width, this.height);

Since there's no constructor overloading (like in Java or C++), Dart offers named constructors:

class MyClassWithConst {
  /* ... */
  MyClassWithConst(this.width, this.height);

  MyClassWithConst.withZeros() {
    this.width = 0;
    this.height = 0;
  }
}

Note that constructors aren't inherited from superclasses and you don't even have to define any constructor and Dart will use a default one for you (just the class name with no parameters).

Exceptions

Dart supports throwing exceptions when something goes wrong. The logic behind this is just like in any other language, so it probably doesn't require any further explanation:

throw new Exception("It's broken");

Catching exceptions is based on typical try, catch, and final statements. You can catch only specific exceptions or make general catch statements for all exceptions:

try {
  brokenFunction();
} on MyOwnException {
  itsNotThatBad();
} catch (e) { // 
  itsPrettyBad();
} finally {
  cleanup();
}

Custom defined exceptions have to inherit from the Exception class.

Using static types

It might look like it's better to always specify variable types everywhere. The rule of thumb here is to use types when it's unclear what the variable type is, including return types. For example, look at the following code:

GreetingsManager gm = new GreetingsManager();

Instead of writing the class name twice, we use the following:

var gm = new GreetingsManager();

The static analyzer knows that the gm variable is assigned to a GreetingsManager class, so it's fine to use just var. This also applies when iterating collections:

List<String> colors = ['red', 'green', 'blue'];
for (var color in colors) { }

We don't need to declare String color because this is obvious from the List<String> declaration.

The same approach is recommended when using void and dynamic. If there's no good reason to specify them, just omit them.