Chapter 3. Structuring Code with Classes and Libraries

In this chapter, we will look at the object-oriented nature of Dart. If you have prior knowledge of an OO language, most of this chapter will feel familiar. Although, coding classes is more succinct while introducing some nice new features such as factory constructors, and generalizing the use of interfaces. If you come from the JavaScript world, you will start to realize that classes can really structure your application.

Data mostly comes in collections. Dart has some neat classes to work with collections and they can be used for any type of collections. This is why they are called generic. As soon as you get a few code files in your project, structuring them by making libraries would become essential for code maintainability. Also, your code would probably use existing libraries written by other developers; to make it easy, Dart has its own package manager called pub. Automating the testing of code on a functional level will be done with a built-in unit test library.

We will look at the following topics:

  • Using classes and objects
  • Collection types and generic classes
  • Structuring your code using libraries
  • Managing library dependencies with pub
  • Unit testing in Dart

We will wrap it all up in a small but useful project to calculate word frequencies in an extract of text.

A touch of class – how to use classes and objects

We saw in Chapter 1, Dart – A Modern Web Programming Language, how a class contains members such as properties, a constructor, and methods (refer to banking_v2.dart). For those familiar with the classes in Java or C#, it's nothing special and we can already see certain simplifications:

  • The short constructor notation lets the parameter values flow directly into the properties:
    BankAccount(this.owner, this.number, this.balance) { … }
  • The this keyword is necessary here and refers to the actual object (being constructed), but it is rarely used elsewhere (only when there is a name conflict). Initialization of instance variables can also be done in the so-called initializer list, like in this shorter version of the constructor:
    BankAccount(this.owner, this.number, this.balance): dateCreated = new DateTime.now();
  • The variables are initialized after the colon (:) and are separated by a comma. You cannot use the this keyword in the initializer expression. If nothing else needs to be done, the constructor body can be left out.
  • The properties have automatic getters to read the value (as in ba.balance) and, when they are not final or constant, they also have a setter method to change the value (as in balance += amount).

Tip

You can start out by using dynamic typing (var) for properties, especially when you haven't decided what type a property will become. As development progresses, though, you should aim to change the dynamic types into strong types that give more meaning to your code and can be validated by the tools.

Properties that are Boolean values are commonly named with is at the beginning, for example, isOdd.

A class has a default constructor when there are no other constructors defined. Objects (instances of the class) are made with the new keyword and an is object of the type of the class. We can test this with the is operator; for example, if ba has the BankAccount type, then the following will be true: ba is BankAccount. Single inheritance between the classes is defined by the extends keyword; the base class of all the classes being Object.

Member access uses the dot (.) notation, as in ba.balance or ba.withdraw(100.0). A class can contain objects that are instances of the other classes: a feature known as composition (aggregation). For example, we could decide at a later stage that the String owner in the BankAccount class should really be an object of a Person class with many other properties and methods.

A neat feature to simplify the code is the cascade operator (..). With it, you can set a number of properties and execute methods on the same object, for example, on the ba object in the following code (it's not chaining operations!):

ba
  ..balance = 5000.0
  ..withdraw(100.0)
  ..deposit(250.0);

We'll focus on what makes Dart different and more powerful than common OO languages.

Visibility – getters and setters

What about the visibility or access of class members? They are public by default, but if you name them beginning with an underscore (_), they will become private. However, private in Dart does not mean that it will only be visible in its class; a private field (property), for example, _owner is visible in the entire library in which it is defined, but not in the client code that uses the library.

For the moment, this means that it is accessible in the code file where it is declared, because a code file defines an implicit library. The entire picture will become clear in the coming section on the libraries. A good feature that enhances productivity is that you can begin with public properties, as in project_v1.dart. A Project object has a name and a description and we will use the default constructor:

main() {
  var p1 = new Project();
  p1.name = 'Breeding';
  p1.description = 'Managing the breeding of animals';
  print('$p1'),
  // prints: Project name: Breeding - Managing the breeding of animals
}

class Project {
  String name, description;
  toString() => 'Project name: $name - $description';
}

Suppose now that new requirements arrive; the length of a project name must be less than 20 characters and, when printed, the name must be in capital letters. We want the Project class to be responsible for these changes, so we will create a private property, _name, and the get and set methods to implement the requirements (refer to project_v2.dart):

class Project {
  String _name; // private variable
  String description;

  String get name => _name == null ? "" :_name.toUpperCase();
  set name(String prName) {
    if (prName.length > 20)
      throw 'Only 20 characters or less in project name';
    _name = prName;
  }

  toString() => 'Project name: $name - $description'; 
}

The get method makes sure that, in case _name is not yet filled in, an empty string is returned.

The code that already existed in main (or, in general, the client code that uses this property) does not need to change; it now prints Project name: BREEDING - Managing the breeding of animals. If a project name that is too long is given, the code will generate an exception.

Tip

Start your class code with public properties and, later, change some of them to private if necessary with getters and/or setters without breaking the client code!

A getter (and a setter) can also be used without a corresponding property instead of simplifying the code again, such as the getters for area, perimeter, and diagonal in the Square (square_v1.dart) class:

import 'dart:math';

void main() {
  var s1 = new Square(2);
  print('${s1.perimeter}'), // 8
  print('${s1.area}'),      // 4
  print('${s1.diagonal}'),  // 2.8284271247461903
}

class Square {
  num length;
  Square(this.length);

  num get perimeter => 4 * length;
  num get area => length * length;
  num get diagonal => length * SQRT2;
}

SQRT2 is defined in dart:math. The new properties cannot be changed, because this is only a getter (they are properties derived from other properties). Dart doesn't do function overloading because of optional typing, but allows operator overloading, redefining a number of operators (such as ==, >=, >, <=, and <—all the arithmetic operators—as well as [] and []=). For example, examine the > operator in square_v1.dart:

bool operator >(Square other) => length > other.length? true : false;

If s1 and s2 are Square objects, we can now write the code like this: if (s2 > s1) { … }.

Tip

Use overloading of operators sparingly and only when it seems a good fit and would be unsurprising to fellow developers.

Types of constructors

All OO languages have class constructors, but Dart has only few kinds of constructors covered in the following sections.

Named constructors

Because there is no function overloading, there can be only one constructor with the class name (the so-called main constructor). So, if we want more, we must use named constructors, which take the ClassName.constructorName form. If the main constructor does not have any parameters, it is called a default constructor. If the default constructor does not have a body of statements such as BankAccount();, it can be omitted. If you don't declare a constructor, a default constructor will be provided for you. Suppose we want to to create a new bank account for a person by copying data from another of his / her bank accounts, for example, the owner's name. We could do this with the BankAccount.sameOwner named constructor (refer to banking_v3.dart):

BankAccount.sameOwner(BankAccount acc)  {
  owner = acc.owner;
}

We could also do this with the initializer version:

BankAccount.sameOwner(BankAccount acc): owner = acc.owner;

When we make an object via this constructor and print it out, we get:

Bank account from John Gates with number null and balance null

A constructor can also redirect to the main constructor by using the this keyword, as follows:

BankAccount.sameOwner2(BankAccount acc): this(acc.owner, "000-0000000-00", 0.0);

We initialize the number and balance to dummy values, because this() has to provide three arguments for the three parameters of the main constructor.

Factory constructors

Sometimes, we don't want a constructor to always make a new object of the class; perhaps we want to return an object from a cache or create an object from a subtype instead. The factory constructor provides this flexibility, which is extensively used in the Dart SDK. In factory_singleton.dart, we use this ability to implement the singleton design pattern (for a general intro to design patterns, see https://en.wikipedia.org/wiki/Software_design_pattern) in which there can be only one instance of the class:

class SearchEngine {
  static SearchEngine theOne;                             (1)
  String name;

  factory SearchEngine(name) {                            (2)
    if (theOne == null) {
      theOne = new SearchEngine._internal(name);
    }
    return theOne;
  }
// private, named constructor
  SearchEngine._internal(this.name);                      (3)
// static method:
   static nameSearchEngine () => theOne.name;             (4)
}

main() {
  //substitute your favorite search-engine for se1:
  var se1 = new SearchEngine('Google'),                   (5)
  var se2 = new SearchEngine('Bing'),                     (6)
  print(se1.name);                        // 'Google'
  print(se2.name);                        // 'Google'
  print(SearchEngine.theOne.name);        // 'Google'     (7)
  print(SearchEngine.nameSearchEngine()); // 'Google'     (8)
  assert(identical(se1, se2));                            (9)
}

In line (1), the theOne static variable (here, of the SearchEngine type itself, but it could also be of a simple type, such as num or String) is declared: such a variable is the same for all the instances of the class. It is invoked on the class name itself, as in line (7); this is why it is also called a class variable. Likewise, you can have static methods (or class methods) such as nameSearchEngine (line (4)) called in the same way (line (8)).

Tip

Static methods can be useful, but don't create a class containing static methods only to provide common or widely used utilities and functionality; use top-level functions instead.

In lines (5) and (6), two SearchEngine objects se1 and se2 are created through the factory constructor in line (2). This checks whether our theOne static variable already refers to an object or not. If not, a SearchEngine object is created through the SearchEngine._internal named constructor from line (3); if it was already created, nothing is done and the theOne object is returned in both cases. The two SearchEngine objects se1 and se2 are, in fact, the same object, as is proven in line (9). Note that the SearchEngine._internal named constructor is private; a factory invoking a private constructor is also a common pattern.

The const constructors

Two squares created with the same length are different objects in memory. If you want to make a class where each object cannot change, provide it with the const constructors and ensure that every property is const or final, for example, the ImmutableSquare class in square_v1.dart:

class ImmutableSquare {
  final num length;
  static final ImmutableSquare ONE = const ImmutableSquare(1);
  const ImmutableSquare(this.length);
}

Objects are created with const instead of new, using the const constructor in the last line of the class to give length its value:

var s4 = const ImmutableSquare(4);
var s5 = const ImmutableSquare(4);
assert(identical(s4,s5));

Inheritance

Inheritance in Dart comes with no surprises if you know the concept from Java or .NET. Its main use is to reduce the codebase by factoring common code (properties, methods, and so on) into a common parent class. In square_v2.dart, the Square class inherits from the Rectangle class indicated by the extends keyword (line (4)). A Square object inherits the properties from its parent class (see line (1)), and you can refer to the constructors or methods from the parent class with the super keyword (like in line (5)):

main() {
  var s1 = new Square(2);
  print(s1.width);                             (1)
  print(s1.height);
  print('${s1.area()}'), // 4
  assert(s1 is Rectangle);                     (2)
}

class Rectangle {
  num width, height;
  Rectangle(this.width, this.height);
  num area() => width * height;                (3)
}

class Square extends Rectangle {               (4)
  num length;
  Square(length): super(length, length) {      (5)
    this.length = length;
  }
  num area() => length * length;               (6)
}

Methods from the parent class can be overridden in the derived class without special annotations, for example, the area()method (lines (3) and (6)). An object of a child class is also of the type of the parent class (see line (2)) and thus it can be used whenever a parent class object is needed. This is the basis of what is called the polymorphic behavior of objects. All classes inherit from Object, but a class can have only one direct parent class (single inheritance). Constructors are not inherited. An object, the class of this object, its parent class, and so on (until Object) are all searched for the method that is called on. A class can have many derived classes, so an application is typically structured as a class hierarchy tree.

In OO programming, the class composition (with properties representing components / objects of other classes) and inheritance are used to support a divide-and-conquer approach toward problem solving. Class A inherits from class B only when A is a subset of B, for example, a square is a rectangle, a manager is an employee; basically when class B is more generic and less specific than class A. It is recommended that inheritance be used with caution, because an inheritance hierarchy is more rigid in the maintenance of programs than a composition.

Abstract classes and methods

Looking for parent classes is an abstraction process and it can go so far that the parent class we have decided to work with can no longer be fully implemented. That is, it can contain methods that we cannot code at this point by the so-called abstract methods. Extending the previous example to square_v3.dart, we could easily abstract out a Shape parent class. This could contain methods to calculate the area and the perimeter, but they would be empty because we can't calculate them without knowing the exact shape. Other classes such as Rectangle and Square could inherit from Shape and provide the implementation for these methods:

main() {
  var s1 = new Square(2);
  print('${s1.area()}'),       // 4
  print('${s1.perimeter()}'),  // 8
  var r1 = new Rectangle(2, 3);
  print('${r1.area()}'),       // 6
  print('${r1.perimeter()}'),  // 10
  assert(s1 is Shape);
  assert(r1 is Shape);
  // warning + exception in checked mode: Cannot instantiate
  // abstract class Shape
  // var f = new Shape();
}

abstract class Shape {
  num perimeter();
  num area();
}

class Rectangle extends Shape {
  num width, height;
  Rectangle(this.width, this.height);
  num perimeter() => 2 * (height + width);
  num area() => height * width;
}

class Square extends Shape {
  num length;
  Square(this.length);
  num perimeter() => 4 * length;
  num area() => length * length;
}

Also, making instances of Shape isn't very useful, so it is rightfully an abstract class. An abstract class can also have properties and implemented methods, but you cannot make objects from an abstract class unless it contains a factory constructor that creates an object from another class. This can be useful as a default object creator for this abstract class. A simple example can be seen in the factory_abstract.dart file:

void main() {
  Animal an1 = new Animal();                (1)
  print('${an1.makeNoise()}'), // Miauw
}

abstract class Animal {
  String makeNoise();
  factory Animal() => new Cat();            (2)
}

class Cat implements Animal {
  String makeNoise() => "Miauw";
}

Animal is an abstract class and, because we need cats in our app, we decide to give it a factory constructor to make a cat (line (2)). Now, we can construct an object from the Animal class (line (1)) and it will behave like a cat. Note that we must use the implements keyword here to make the relationship between the class and the abstract class (which is also an interface, as we will discuss in the next section). Many of the core types in the Dart SDK are abstract classes (or interfaces), such as num, int, String, List, and Map. They often have factory constructors that redirect to a specific implementation class to make an object.

The interface of a class – implementing interfaces

In Java and .NET, an abstract class without any implementation in its methods is called an interface—a description of a collection of fields and methods—and classes can implement interfaces. Dart's interfaces work differently from Java / C# and there is no need for an explicit interface concept. Here, every class implicitly defines its own interface (also called API), containing all the public instance members of the class (and of any interfaces it implements). The abstract classes of the previous section are also interfaces in Dart. Interface is not a keyword in the Dart syntax, but both the words are used as synonyms. Class B can implement any other class C by providing the code for C's public methods. In fact, the previous example, square_v3.dart, continues to work when we change the extends keyword to implements:

class Rectangle implements Shape {
  num width, height;
  Rectangle(this.width, this.height);
  num perimeter() => 2 * (height + width);
  num area() => height * width;
}

This has the additional benefit that the Rectangle class could now inherit from another class if necessary. Every class that implements an interface is also of that type as is proven by the following line of code (when r1 is an object of class Rectangle):

assert(r1 is Shape);

The extends keyword is much less used than implements, but it clearly has a different meaning too. While using extends, the inheritance chain is searched for a called method, not the implemented interfaces, as is the case while using implements.

Implementing an interface is not restricted to one interface. A class can implement many different interfaces, for example, class Cat implements Mammal, Pet { ... }. In this new vision, where every class defines its own interface, abstract classes (which could be called explicit interfaces) are of much less importance (in fact, the abstract keyword is optional; leaving it off only gives a warning of its unimplemented members). This interface concept is more flexible than the one in most OO languages, and it doesn't force us to define our interfaces right from the start of a project. The dynamic type, which we discussed briefly in the beginning of this chapter, is the base interface that every other class (also Object) implements. However, it is an interface without properties or methods and cannot be extended.

To summarize it, interfaces are used to describe functionality that is shared (implemented) by a number of classes. The implementing classes must fulfill the interface requirements. Coding against interfaces is an excellent way to provide more coherence and structure in your class hierarchy.

Polymorphism and the dynamic nature of Dart

Because Dart fully implements all the OO principles, we are able to write polymorphic code in which an object can be used wherever something of its type, the type of its parent classes, or the type of any of the interfaces it implements is needed. We will see this in action in polymorphic.dart:

main() {
  var duck1 = new Duck();
  var duck2 = new Duck('blue'),
  var duck3 = new Duck.yellow();
  polytest (new Duck()); // Quack   I'm gone, quack!      (1)
  polytest (new Person());// human_quack  I am a person swimming                (2)
}

polytest(Duck duck) {                                     (3)
  print('${duck.sayQuack()}'),
  print('${duck.swimAway()}'),
}

abstract class Quackable {
  String sayQuack();
}

class Duck implements Quackable {
  var color;
  Duck([this.color='red']);
  Duck.yellow() { this.color = 'yellow';}

  String sayQuack() => 'Quack';
  String swimAway() => "I'm gone, quack!";
}

class Person implements Duck {                           (4)
  sayQuack() => 'human_quack';
  swimAway() => 'I am a person swimming';                (5)

  noSuchMethod(Invocation invocation) {                  (6)
    if (invocation.memberName == new Symbol("swimAway"))print("I'm not really a duck!");
  }
}

The top-level polytest function in line (3) takes anything that is in Duck as an argument. In this case, this is not only a real duck, but also a person, because the Person class also implements Duck (line (4)). This is polymorphism. This property of a language permits us to write code that is generic in nature; using objects of interface types, our code can be valid for all the classes that implement the interface used.

Another property shows that Dart also resembles dynamic languages such as Ruby and Python; when a method is called on an object, its class, parent class, the parent class of the parent class, and so on (until the class Object), are searched for the method called. If it is found nowhere, Dart searches the class tree from the class to the Object class again for a method called noSuchMethod().

The Object has this method and its effect is to throw noSuchMethodError. We can use it to our advantage by implementing the method in our class itself; see line (6) in the Person class (the argument mirror is of the Invocation type; its memberName property is the name of the method called and its namedArguments property supplies a Map with the method's arguments). If we now remove line (5) so that Person no longer implements the swimAway()method, the Editor will give us a warning:

Concrete class Person has unimplemented members fromDuck: String swimAway().

However, if we now execute the code, the I'm not really a duck! message would be printed when print('${duck.swimAway()}') is called for the Person object. Because swimAway() didn't exist for the Person class or any of its parent classes, noSuchMethod is searched, found in the class itself, and executed. noSuchMethod can be used to do what is generally called metaprogramming in the dynamic languages arena, giving our applications greater flexibility to efficiently handle new situations.

Collection types and generics

In the Built-in types and their methods section in Chapter 2, Getting to Work with Dart, we saw that very powerful data structures such as List and Map are core to Dart, not something that were added afterwards in a separate library like in Java or .NET.

Typing collections and generics

How can we check the type of the items in a List or Map? A list created either as a literal or with the default constructor can contain items of any type, like the following code shows (refer to generics.dart):

var date = new DateTime.now();
// untyped List (or a list of type dynamic):
var lst1 = [7, "lucky number", 56.2, date];
print('$lst1'), // [7, lucky number, 56.2,
  // 2013-02-22 10:08:20.074]
var lst2 = new List();
lst2.add(7);
lst2.add("lucky number");
lst2.add(56.2);
lst2.add(date);
print('$lst2'), // [7, lucky number, 56.2,
  // 2013-02-22 10:08:20.074]

While this makes for very versatile lists most of the time, you know that the items will be of a certain type, such as int, String, BankAccount or even List, themselves. In this case, you can indicate the E type between < and > in this way: <E>. An example is shown in the following code:

var langs = <String>["Python","Ruby", "Dart"];
var langs2 = new List<String>();                        (1)
langs2.add("Python");
langs2.add("Ruby");
langs2.add("Dart");
var lstOfString = new List<List<String>>();             (2)

Note

Don't forget () at the end of lines (1) and (2), because this calls the constructor!

With this, Dart can control the items for us; langs2.add(42); gives us a warning and a TypeErrorImplementation exception when it is run in the checked mode:

type 'int' is not a subtype of type 'String' of 'value'

Here, value means 42. However, when we run in the production mode, this code runs just fine. Again, indicating the type helps us to prevent possible errors and, at the same time, document the code.

Why is the special <> notation also used as List<E> in the API documents for list? This is because all of the properties and methods of List work for any E type. This is why the List<E> type is called generic (or parameterized). The E formal type parameter stands for any possible type.

The same goes for Maps; a Map is, in fact, a generic Map<K,V> type, where K and V are formal type parameters for the types of the keys and values respectively, giving us the same benefits as the following code will demonstrate:

var map = new Map<int, String>();
map[1] = 'Dart';
map[2] = 'JavaScript';
map[3] = 'Java';
map[4] = 'C#';
print('$map'), // {1: Dart, 2: JavaScript, 3: Java, 4: C#}
map['five'] = 'Perl'; // String is not assignable to int  (3)

Again, line (3) gives us a TypeError exception in the checked mode, not in the production mode. We can test the generic types like this:

print('${langs2 is List}'), // true
print('${langs2 is List<String>}'), // true               (4)
print('${langs2 is List<double>}'), // false              (5)

We can see that, in line (5), the type of the List is checked; this check works even in the production mode! (Uncheck the Run in Checked Mode checkbox in Run | Manage Launches and click on Apply to see it in action.) This is because generic types in Dart (unlike in Java) are reified; their type information is preserved during runtime, so you can test the type of collection even in the production mode. Note, however, that this is the type of the collection only. While adding the langs2.add(42); statement (which executes fine in the production mode), the check in line (4) still gives us the true value. If you want to check the types of all the elements in a collection in the production mode, you will have to do it for each element, individually, as shown in the following code:

for (var s in langs2) {
  if (s is String) print('$s is a String'),
  else             print ('$s is not a String!'),
}
// output:
//  Python is a String
//  Ruby is a String
//  Dart is a String
//  42 is not a String!

Checking the types of generic Lists gives expected results mostly:

print(new List
<String>() is List<Object>);                 // true  (1)
print(new List<Object>() is List<String>);   // false (2)
print(new List<String>() is List<int>);      // false (3)
print(new List<String>() is List);           // true  (4)
print(new List() is List<String>);           // true  (5)

Line (1) is true, because Strings (as everything) are Objects. Line (2) is false, because not every Object is a String. Line (3) is false, because Strings are not integers. Line (4) is true, because Strings are also of the dynamic general type. Line (5) can be a surprise: dynamic is String. This is because those generic types without type parameters are considered to be substitutable (subtypes of) for any other version of the generic type.

The collection hierarchy and its functional nature

Apart from List and Map, there are other important collection classes, such as Queue and Set, among the others specified in the dart:collection library; most of them are generic. We can't review them all here, but the most important ones have the following relations (an arrow is UML notation for "is a subclass of" (extends in Dart)):

The collection hierarchy and its functional nature

The collection hierarchy

List and Queue are classes that inherit from Iterable, and Set inherits from IterableBase; all these are abstract classes. The Map class is also abstract and forms on its own the root of a whole series of classes that implement the containers of values associated with keys, sometimes also called dictionaries. Put simply, the Iterable interface allows you to enumerate (or iterate, that is, read but not change) all the items of a collection one by one using what is called an iterator. As an example, you can make a collection of the numbers 0 to 9 by making an iterator with:

var digits = new Iterable.generate(10, (i) => i);

The iteration can be performed with the for (item in collection) statement:

for (var no in digits) {
  print(no);
} // prints 0 1 2 3 4 5 6 7 8 9 on successive lines

This prints all the numbers from 0 to 9, successively. Members such as isEmpty, length, and contains(), which we saw in action with list (refer to the lists.dart file), are already defined at this level, but there is a lot more. Iterable also defines very useful methods for filtering, searching, transforming, reducing, chaining, and so on. This shows that Dart has a lot of characteristics of a functional language: we see lots of functions taking functions as parameters or returning functions. Let's look at some of the examples applied to a list by applying toList() to our Iterable object digits:

var digList = digits.toList();

An even shorter and more functional version than for...in is forEach, which takes a function as a parameter that is applied to every i item of the collection in turn. In the following example, a function that simply prints the item is shown:

digList.forEach((i) => print('$i'));

It is called an anonymous function, because it has no name.

Use forEach, whenever you don't need the index of the item in the loop. This also works for Maps, for example, to print out all the keys in the following map:

Map webLinks =   {  'Dart': 'http://www.dartlang.org/',
  'HTML5': 'http://www.html5rocks.com/' };
webLinks.forEach((k,v) => print('$k')); // prints: Dart   HTML5

If you want the first or last element of a List, use the corresponding functions.

If you want to skip the first n items, use skip(n), or skip by testing on a condition with skipWhile(condition):

var skipL1 = digList.skip(4).toList();
print('$skipL1'), // [4, 5, 6, 7, 8, 9]
var skipL2 = digList.skipWhile((i) => i <= 6).toList();
print('$skipL2'), // [7, 8, 9]

The take and takeWhile functions do the opposite; they take the given number of items or the items that fulfill the condition:

var takeL1 = digList.take(4).toList();
print('$takeL1'), // [0, 1, 2, 3]
var takeL2 = digList.takeWhile((i) => i <= 6).toList();
print('$takeL2'), // [0, 1, 2, 3, 4, 5, 6]

If you want to test whether any of the items fulfill a condition, use any; to test whether all of the items do so, use every:

var test = digList.any((i) => i > 10);
print('$test'),  // false
var test2 = digList.every((i) => i < 10);
print('$test2'),  // true

Suppose you have a List and you want to filter out only the items that fulfill a certain condition (this is a function that returns a Boolean called a predicate), in our case, the even digits. Here, is how it's done:

var even = (i) => i.isEven;                           (1)
var evens = digList.where(even).toList();             (2)
print('$evens'),  // [0, 2, 4, 6, 8]                  (3)
evens = digList.where((i) => i.isEven).toList();      (4)
print('$evens'),  // [0, 2, 4, 6, 8]

We use the isEven property of int to construct an anonymous function in line (1). It takes the i parameter to test its evenness, and we assign the anonymous function to a function variable called even. We then pass this function as a parameter to where and make a list of the result in line (2). The output in line (3) is what we expect.

It is important to note that where takes a function that, for each item, tests a certain condition and thus returns true or false. In line (4), we write it more tersely in one line, which makes it appropriate and elegant for short predicate functions. Why do we need the toList() function to be called in this and the previous functions? Because where (and the other Iterable methods) returns a so-called lazy Iterable method. Calling where alone does nothing; it is toList() that actually performs the iteration and stuffs the results in a list (try it out: if you leave out toList(), you will get an instance of WhereIterable).

If you want to apply a function to every item and form a new List with the results, you can use the map function; in the following example, we will triple each number:

var triples = digList.map((i) => 3 * i).toList();
print('$triples'), // [0, 3, 6, 9, 12, 15, 18, 21, 24, 27]

Another useful utility is to apply a given operation with each item in succession combined with a previously calculated value. Concretely say, we want to sum all the elements of our List. We can, of course, do this in a for loop, accumulating the sum in a temporary variable:

var sum = 0;
for (var i in digList) {
  sum += i;
}
print('$sum'), // 45

Dart provides a more succinct and functional way to do this kind of manipulation with the reduce function (eliminating the need for a temporary variable):

var sum2 = digList.reduce((prev, i) => prev + i);
print('$sum2'), // 45

We can apply reduce to obtain the minimum and maximum of a numeric List, as follows:

var min = digList.reduce(Math.min);
print('minimum: $min'), // 0
var max = digList.reduce(Math.max);
print('maximum: $max'), // 9

For this to work, we need to import the math library:

import 'dart:math' as Math;

We could do this because min and max are defined for numbers, but what about the other types? For this, we need to be able to compare the two List items: i1 and i2. If i2 is greater than i1, we will know the min and max values of the two and be able to sort them. Dart has this intrinsically defined for the basic int, num, String, Duration, and Date types. So, in our example with the int types, we can simply write:

var lst = [17, 3, -7, 42, 1000, 90];
lst.sort();
print('$lst'), // [-7, 3, 17, 42, 90, 1000]

If you look up the definition of sort(), you will see that it takes, as an optional argument, a function of the int type, compare(E a, E b), belonging to the Comparable interface. Generally, this will be implemented as follows:

  • If a < b, return -1
  • If a > b, return 1
  • If a == b, return 0

In the following code, we will use the preceding logic to obtain the minimum and maximum of a List of Strings:

var lstS = ['heg', 'wyf', 'abc'];
var minS = lstS.reduce((s1,s2) => s1.compareTo(s2) < 0 ? s1 : s2);
print('Minimum String: $minS'), // abc

In a general case, we need to implement compareTo ourselves for the element type of the list. It turns out that the preceding code lines can then be used to obtain the minimum and maximum of a List of a general type! To illustrate this, we will construct a list of persons; these are objects of a very simple Person class:

class Person {
  String name;
  Person(this.name);
}

We will make a List of the four Person objects and try to sort it, as shown in the following code:

var p1 = new Person('Peeters Kris'),
var p2 = new Person('Obama Barak'),
var p3 = new Person('Poetin Vladimir'),
var p4 = new Person('Lincoln Abraham'),
var pList = [p1, p2, p3, p4];
pList.sort();

We will then get the following exception:

type 'Person' is not a subtype of type 'Comparable'.

This means that the Person class must implement the Comparable interface by providing the code for the compareTo method. Because String already implements this interface, we can use the compareTo method for the person's name:

class Person implements Comparable{
  String name;
  Person(this.name);
  // many other properties and methods
  compareTo(Person p) => name.compareTo(p.name);
}

Then, we can get the min and max values and sort our Person list in place simply by:

var minP = pList.reduce((s1,s2) => s1.compareTo(s2) < 0 ? s1 : s2);
print('Minimum Person: ${minP.name}'), // Lincoln Abraham
var maxP = pList.reduce((s1,s2) => s1.compareTo(s2) < 0 ? s2 : s1); 
print('Maximum Person: ${maxP.name}'), // Poetin Vladimir

pList.sort();
pList.forEach((p) => print('${p.name}'));

The preceding code will print the following output (on successive lines):

Lincoln Abraham   Obama Barak   Peeters Kris   Poetin Vladimir

To use Queue, your code must import the collection library by using import 'dart:collection';, because this is the library the class is defined in. It is another collection type differing from a List in a way that the first (head) or the last item (tail) are important. You can add an item to the head with addFirst or to the tail with add or addLast, or you can remove an item with removeFirst or removeLast:

var langsQ = new Queue();
langsQ.addFirst('Dart'),
langsQ.addFirst('JavaScript'),
print('${langsQ.elementAt(1)}'), // Dart
var lng = langsQ.removeFirst();
assert(lng=='JavaScript'),
langsQ.addLast('C#'),
langsQ.removeLast();
print('$langsQ'), // {Dart}

You have access to the items in a Queue by the index with elementAt(index) and forEach is also available. For this reason, Queues are ideal when you need a first-in first-out (FIFO) data structure or a last-in first-out (LIFO, which is called a stack in most languages) data structure.

Lists and Queues allow duplicate items. If you don't need ordering and your requirement is to only have unique items in a collection, use a Set type:

var langsS = new Set();
langsS.add('Java'),
langsS.add('Dart'),
langsS.add('Java'),
langsS.length == 2;
print('$langsS'), // {Dart, Java}

Again, Sets allow for the same methods as List and Queue from their place in the collection hierarchy (see the following diagram). They also have the specific intersection method that returns the common elements between a Set and another collection.

Here is a handy flowchart to decide which data structure to use:

The collection hierarchy and its functional nature

Choosing a collection type

Note

Maps have unique keys (but not values) and Sets have unique items, while Lists and Queues do not. Lists are ideal for arbitrary access to items anywhere in the collection (by index), but changing their size can be costly. Queues are the type to use if you mainly want to operate on the head or tail of the collection.

Structuring your code using libraries

Using classes, extending them, and implementing interfaces are the way to go to structure your Dart code. However, how do we group together a number of classes, interfaces, and top-level functions that are coupled together? To package an application or to create a shareable code base, we use a library. The Dart SDK already provides us with some 30 utility libraries, such as dart:core, dart:math, and dart:io. You can look them up in your Editor by going to Help | API Reference or via the http://api.dartlang.org URL. All the built-in libraries have the dart: prefix. We have seen them in use a few times and know that we have to import them in our code as import 'dart:math'; in prorabbits_v7.dart. Web applications will always import dart:html (dart:core is the most fundamental library and so is imported automatically). Likewise, we can create our own libraries and let other apps import them to use their functionality.

To illustrate this, let's make use of our rabbit breeding application (perhaps, there is a market for this app after all). For an app this simple, this is not needed, of course. However, every Dart app that contains a main() function is also a library, even when this is not indicated. We make a new app called breeding that could contain all kinds of breeding calculations. We group together all the constants that we will need in a file called constants.dart and move the function that calculates the rabbit breeding to a file named rabbits.dart in a subfolder called rabbits. All the files now have to declare how they are a part of the library. There is one code file (the library file in the bin subfolder; its file icon in the Editor is shown in bold) that contains the library keyword; in our example, it is breeding.dart in line (1):

library breeding;                              (1)

import 'dart:math';                            (2)

part 'constants.dart';	                         (3)
part 'rabbits/rabbits.dart';

void main() {                                  (4)
  print("The number of rabbits increases as:
");
  for (int years = 0; years <= NO_YEARS; years++) {
    print("${calculateRabbits(years)}");
  }
}

A library needs a name; here, it is breeding (all in lowercase and not in quotes). Other apps can import our library through this name. This file also contains all the necessary import statements (line (2)) and sums up (in no particular order) all the source files that together constitute of the library. This is done with the part keyword, followed by the quoted (relative) pathname to the source file, for example, when rabbits.dart resides in a subfolder called rabbits, this will be written as:

part 'rabbits/rabbits.dart';

However, everything is simple if all the files of a library reside in one folder. So, the library file presents an overview of all the part files it is split in; if needed, we can structure our library with subfolders, but Dart sees all this code as a single file. Furthermore, all the library source files need to indicate that they are a part of the library (we show only rabbits.dart here); again, the library name is not quoted (line (1)):

part of breeding;                             (1)

String calculateRabbits(int years) {
  calc() => (2 * pow(E, log(GROWTH_FACTOR) * years)).round().toInt();
  
  var out = "After $years years:	 ${calc()} rabbits";
  return out;
}

Note

GROWTH_FACTOR is defined in the constants.dart file.

All these statements (library, import, part, and part of) need to appear at the top before any other code. The Dart compiler will import a specific source file only once, even when it is mentioned several times. If there is a main entry function in our library, it must be in the library file (line (4)); start the app to verify that we obtained the same breeding results as in our previous versions. A library that contains main() is also a runnable app in itself but, in general, a library does not need to contain a main() function. The part of annotation enforces that a file can only be a part of one library. Is this a restriction? No, because it strengthens the principle that the code must not be duplicated. If you have a collection of business classes in an app, group them in their own library and import them into your app; this way, these classes will be reusable.

Tip

You can start coding your app (library) in a single file. Gradually, you will begin to discover units of the functionality of classes and/or the functions that belong together; then, you can move these into part files, while the library file, which contains the part statements, structures the whole.

Using a library in an app

To show how we can use our newly made library in another app, create a new app_breeding application. In its startup file (app_breeding.dart), we can call our library, as shown in the following code:

import '../../breeding/bin/breeding.dart';          (1)

int years;

void main() {
  years = 5;
  print("The number of rabbits has attained:");
  print("${calculateRabbits(years)}");
}
// Output:
//The number of rabbits has attained:
//After 5 years:   1518750 rabbits

The import statement in line (1) points to the main file of our library relative, in the file system, to the .dart file we are in (two folders level up with two periods (..) and then into the bin subfolder of breeding). As long as your libraries retain the same relative position to your client app (while deploying it in production), it will work. You can also import a library from a (remote) website using a URL in the following manner:

import 'http://www.breeding.org/breeding.dart';

Absolute file paths in import are not recommended, because they break too easily while deploying. In the next section, we will discuss the best way of importing a library by using the package manager called pub.

Resolving name conflicts

If you only want one or a few items (variables, functions, and classes) from a library, you will have the option of only importing these by enumerating them after show:

import 'library1.dart' show var1, func1, Class1;

The inverse can also be done; if you want to import everything from the library excluding these items, use hide:

import 'library1.dart' hide var1, func1, Class1;

We know that everything in a Dart app must have a unique name or, to put it another way, there can be no name conflicts in the app's namespace. What if we have to import into our app two libraries that have the same names for some of their objects? If you only need one of them, you can use show and/or hide. However, what if you need both? In such a case, you can give one of the libraries an alias and differentiate between the two using this alias as a prefix. Suppose library1 and library2 have an object A; you can differentiate between the two as follows:

import 'library1.dart';          // contains class A
import 'library2.dart' as libr2; // contains class A

var obj1 = new A();             // Use A from library1.
var obj2 = new libr2.A();       // Use A from library2.

Use this feature only when you really have to, for example, to solve name conflicts or aid readability. Finally, the export command (possibly combined with show or hide) gives you the ability to combine (parts of) libraries, refer to the export app.

Suppose liba.dart contains the following code:

library liba;
abc() => 'abc from liba';
xyz() => 'xyz from liba';

Additionally, suppose libb.dart contains the following code:

library libb;
import 'liba.dart';
export 'liba.dart' show abc;

Then, if export.dart imports libb, it will know the abc method but not the xyz method:

import 'libb.dart';
void main() {
  print('${abc()}'), // abc from liba
  // xyz();  // cannot resolve method 'xyz'
}

Visibility of objects outside a library

In the A touch of class – how to use classes and objects section, we mentioned that starting a file name with _ makes it private for the library (only known in the library itself). This is the case for all objects: variables, functions, classes, methods, and so on. Now, we will illustrate this in our breeding library.

Suppose breeding.dart contains two top-level variables:

String s1 = 'the breeding of cats';               (1)
var _s2   = 'the breeding of dogs';               (2)

We can use them both in main(), but also anywhere else in the library, for example, in rabbits.dart:

String calculateRabbits(int years) {
  print('$s1 and $_s2'),
  //…
  return out;
}

However, if we try to use them in the breeding.dart app, which imports breeding, we will get a warning in line (3) of the following code in the Editor; it will say cannot resolve _s2; s1 is visible but _s2 is not:

void main() {
  years = 5;
  // …
  print('$s1 and $_s2'),                          (3)
}

An exception occurs when the code is run (both in the checked and production modes). Note that, in lines (1) and (2), we typed the s1 public variable as String, while the _s2 private variable was left untyped. This is a general rule: give the publicly visible area of your library strong types and signatures. Privacy is an enhancement for developers used to JavaScript, but people coming from the OO arena will certainly ask why there is no class privacy. There are probably a number of reasons: classes are not that primordial in Dart as in OO languages, Dart has to compile to JavaScript, and so on. Class privacy is not needed to the extent usually imagined, and if you really want to have it in Dart, you can do it. Let the library only contain the class that has some private variables; these are visible only in this class, because other classes or functions are outside of this library.

Managing library dependencies with pub

Often, your app depends on libraries (packages) that are stored in the cloud (in the pub repository, or the GitHub repository, and so on). In this section, we will discuss how to install such packages and make them available to your code.

In the web version of our rabbits program (prorabbits_v3.dart) in Chapter 1, Dart – A Modern Web Programming Language, we discussed the use of the pubspec.yaml file. This file is present in every Dart project and contains the dependencies of our app on external packages. The pub tool takes care of installing (or updating) the necessary packages: right-click on the selected pubspec.yaml file and choose Pub Get (or Upgrade in case you need a more recent version of the packages). Alternatives are to select the folder name of the app, go to Tools | Pub Get, and double-click on the .yaml file (a screen called Pubspec Details appears and lets you change the contents of the file itself; this screen contains a section called Pub Actions, where you will find a link to Run Pub get). It even automatically installs the so-called transitive dependencies: if the package to install needs other packages, they will also be installed.

Let's prepare for the next section on unit testing by installing the test package with the pub tool. Create a new command-line application and call it unittest_v1. When you open the Pubspec screen, you see no dependencies. However, at the bottom, there is a tab called Source to go to the text file itself. This shows us:

name: unittest_v1
description: A sample command-line application
dependencies:
  test: any

We see that our app depends on the test package. If we now run Pub Get, we will see that a folder called packages will appear, containing in it a folder called test of the complete source of the requested package. The same subfolders appear under the bin folder. If needed, the pub install command can also be run outside the Editor from the command line. Pub installs the test package from its https://pub.dartlang.org/ central repository, as you can see in the following screenshot. Another pubspec.lock file is also created (or updated); this file is used by the pub tool and contains the version info of the installed packages (don't change anything in here). In our example, this contains:

# Generated by pub. See: http://pub.dartlang.org/doc/glossary.html#lockfile
         {"packages":{"test":{"version":"0.12.0","source":"hosted",
  "description":"test"},"meta":{"version":"0.12.0","source":
    "hosted","description":"meta"}}}     

The following screenshot shows the configuration information for pubspec.yaml:

Managing library dependencies with pub

Configuring pub specifications for the app

The Pubspec screen, as you can see in the preceding screenshot, also gives you the ability to change or fill in the complementary app info such as Name, Author, Version, Homepage, SDK version, and Description. The Version field is of particular importance; with it, you can indicate that your app needs a specific version of a package (such as 2.1.0) or a major version number of 1 (>= 1.0.0 < 2.0.0); it locks your app to these versions of the dependencies. To use the installed test package, write the following code line at the top of test_v1.dart:

import 'package:test/unittest.dart';

The path to a Dart source file after package: is searched for in the packages folder. As a second example and in preparation for the next chapter, we will install the dartlero package from GitHub (although the unittest_v1.dart program will not use its specific functionality). We will then add a dependency called dartlero via the pubspec screen; any version would be good. Simply choose git from the source list and fill in https://github.com/dzenanr/dartlero for the path. Save this and then run Pub Install. Pub will clone the project from GitHub, install it in the packages folder, and update the pubspec.lock file. To make it known to your app, use the following import statement:

import 'package:dartlero/dartlero.dart';

The pub publish command checks whether your package conforms to certain conditions and uploads it to the pub's central repository at pub.dartlang.org.

Tip

Dart Editor stores links to the installed packages for each app; these become invalid when you move or rename your code folders. If the Editor gives you the Cannot find referenced source: package: somepkg/pkg.dart error, close the app in the Editor and restart the Editor. In most cases, the problem would be solved. However, if not, clean out the Editor cache by deleting everything in C:usersyournameDartEditor. When you reopen the app in the Editor, the problem will be solved.

Here is a summary of how to install the packages:

  1. Change pubspec.yaml and add dependencies through the Details screen.
  2. Run the pub get command.
  3. Add an import statement to your code for every installed package.

Unit testing in Dart

Dart has a built-in unit test framework. We learned how to import it in our app in the previous section. Every real app and, certainly, the ones that you're going to deploy somewhere should contain a sufficient amount of unit tests. Test programs will normally be separated from the main app code, residing in their own directory called test. Unit testing offers quite a lot of features; we will apply them in the forthcoming projects. Here, we want to show you the basics and we will do so by creating a BankAccount object, making some transactions on it and verifying the results, so we can trust that our BankAccount methods are doing fine (we will continue to work in unittest_v1.dart). Let's create a BankAccount constructor and do some transactions:

var ba1 = new BankAccount("John Gates","075-0623456-72", 1000.0);
ba1.deposit(500.0);
ba1.withdraw(300.0);
ba1.deposit(136.0);

After this, ba1.balance is equal to 1336.0 (because 1000 + 500 – 300 + 136 = 1336). We can test whether our program calculated this correctly with the following statement:

test('Account Balance after deposit and withdrawal', () {
  expect(ba1.balance, equals(1336.0));
});

Or, we can use a shorter statement as follows:

test('Account Balance after deposit and withdrawal', () => expect(ba1.balance, equals(1336.0)));

The test function from the test package takes two parameters:

  • A test name (String); here, this is Account Balance after deposit and withdrawal
  • A function (here, it is anonymous) that calls the expect function; this function also takes two parameters:
    • The value as given by the program
    • The expected value; here, given by equals(expected value)

Now, running the program will give the following output:

test-suite-wait-for-done
PASS: Account Balance after deposit and withdrawal
All 1 tests passed.
test-suite-success

Of course, here PASS indicates that our program is tested successfully. If this were not the case (suppose the balance had to be 1335.0, but the program produced 1336.0), we would get an exception with the Some tests failed message:

test-suite-wait-for-done
FAIL: Account Balance after deposit and withdrawal
  Expected: <1335.0>
       but: was <1336.0>
0 PASSED, 1 FAILED, 0 ERRORS

There would also be a screen output showing you which tests went wrong, the expected (correct) value, and the program value (it is important to note that the tests run after all the other statements in the method have been executed). Usually, you will have more than one test. You can then group them as follows using the same syntax as test:

group('Bank Account tests', () {
  test('Account Balance after deposit and withdrawal', () => expect(ba1.balance, equals(1336.0)));
  test('Owner is correct', () => expect(ba1.owner, equals("John Gates")));
  test('Account Number is correct', () => expect(ba1.number, equals("075-0623456-72")));
});

We can even prepare the tests in a setUp function (here, this would involve creating the account and doing the transactions) and clean up after the tests in a tearDown function (indicating that the test objects are no longer needed):

group('Bank Account tests', () {
  setUp(() {
    ba1 = new BankAccount("John Gates","075-0623456-72", 1000.0);
    ba1.deposit(500.0);
    ba1.withdraw(300.0);
    ba1.deposit(136.0);
  });
  tearDown(() {
    ba1 = null;
  });
  test('Account Balance after deposit and withdrawal', () =>expect(ba1.balance, equals(1336.0)));
  test('Owner is correct', () => expect(ba1.owner, equals("John Gates")));
  test('Account Number is correct', () => expect(ba1.number, equals("075-0623456-72")));
});

The preceding code produces the following output:

test-suite-wait-for-done
PASS: Bank Account tests Account Balance afterdeposit and withdrawal
PASS: Bank Account tests Owner is correct
PASS: Bank Account tests Account Number is correct
All 3 tests passed.
test-suite-success

In general, the second parameter of expect is a so-called matcher that tests whether the value satisfies some constraint. Here are some matcher possibilities: isNull, isNotNull, isTrue, isFalse, isEmpty, isPositive, hasLength(m), greaterThan(v), closeTo(value, delta), inInclusiveRange(low, high), and their variants. For a more detailed discussion of their use, see the documentation at http://www.dartlang.org/articles/dart-unit-tests/#basic-synchronous-tests. We'll apply unit testing in the coming projects, notably in the example that illustrates Dartlero in the next chapter.

Asynchronous programming with async and await

Dart has added new language features to support asynchronous programming in version 1.9: notably, the async functions and await expressions. The code is asynchronous when it calls a possibly time-consuming operation (such as I/O) and continues executing without waiting for the operation to complete.

In the following code snippet, computeResult represents an operation that could take some time to complete and we will wait for its result to return by calling it with await. We will wrap this call inside an anasyncfunc function, which is marked as async: the code that calls anasyncfunc will continue to execute immediately after this call:

anasyncfunc() async {
    var result = await computeResult();
    if (result > limit) {
      // Do something.
    } else {
      // Do something else.
    }
}

We can even use the normal try/catch mechanism for exception handling on the functions called with await, like this:

anasyncfunc() async {
    var result;
    try {
        result = await computeResult();
    }
    catch (e) {
        // react on possible exception e
    }
    // process result
}

If you want to call an asynchronous function inside your app's main() function, the body of main() must be marked as async as follows:

    main() async {
      // call to asynchronous function:
      await computeResult();
    }

These constructs enable us to write code much more elegantly than before so that asynchronous code looks like normal synchronous code. For a more detailed discussion, you can look up https://www.dartlang.org/docs/dart-up-and-running/ch03.html#dartasync---asynchronous-programming.

Project – word frequency

We will now develop systematically a small but useful web app that takes as input an ordinary text and produces as output an alphabetical listing of all the words appearing in the text, together with the number of times they appear (their frequency). For an idea of the typical output, see the following screenshot (word_frequency.dart):

Project – word frequency

The Word frequency app

The user interface is easy: the text is taken from the textarea tag with the id text in the top half. Clicking on the frequency button sets the processing in motion and the result is shown in the bottom half with the id words. Here is the markup from word_frequency.html:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Word frequency</title>
    <link rel="stylesheet" href="word_frequency.css">
  </head>
  <body>
    <h1>Word frequency</h1>
    
    <section>
      <textarea id="text" rows=10 cols=80></textarea>
      <br/>
      <button id="frequency">Frequency</button>
      &nbsp; &nbsp; &nbsp; &nbsp;
      <button id="clear">Clear</button>
      <br/>
      <textarea id="words" rows=40 cols=80></textarea> 
    </section>

    <script type="application/dart"src="word_frequency.dart"></script>
    <script src="packages/browser/dart.js"></script>
  </body>
</html>

In the last line, we will see that the special dart.js script (which checks for the existence of the Dart VM and starts the JavaScript version if it is not found) is also installed by pub. In Chapter 1, Dart – A Modern Web Programming Language, we learned how to connect variables with the HTML elements through the query function:

variable = query('#id')

So, this is what we will do first in main():

// binding to the user interface:
var textArea = querySelector('#text'),
var wordsArea = querySelector ('#words'),
var wordsBtn = querySelector ('#frequency'),
var clearBtn = querySelector ('#clear'),

Our buttons listen to the click events with the mouse; this is translated into Dart as:

wordsBtn.onClick.listen((MouseEvent e) { ... }

Here is the processing we need to do in this click event handler:

  1. The input text is a String; we need to clean it up (remove the spaces and special characters).
  2. Then, we must translate the text into a list of words. This will be programmed in the following function:
    List fromTextToWords(String text)
  3. Then, we traverse through the list and count the number of times each word occurs; this effectively constructs a map. We'll do this in the following function:
    Map analyzeWordFreq(List wordList)
  4. From the map, we will then produce a sorted list for the output area:
    List sortWords(Map wordFreqMap)

With this design in mind, our event handler becomes:

wordsBtn.onClick.listen((MouseEvent e) {
  wordsArea.value = 'Word: frequency 
';
  var text = textArea.value.trim();
  if (text != '') {
    var wordsList = fromTextToWords(text);
    var wordsMap = analyzeWordFreq(wordsList);
    var sortedWordsList = sortWords(wordsMap);
    sortedWordsList.forEach((word) =>
    wordsArea.value = '${wordsArea.value} 
${word}'),
  }
});

In the last line, we appended the output for each word to wordsArea.

Now, we will fill in the details. Removing unwanted characters can be done by chaining replaceAll() for each character, like this:

var textWithout = text.replaceAll(',', '').replaceAll(';', '').replaceAll('.', '').replaceAll('
', ' '),

This is a very ugly code! We can do better by defining a regular expression that assembles all these characters. We can do this with the W expression that represents all the noncharacters (letters, digits, or underscores). Then, we will only have to apply replaceAll once:

List fromTextToWords(String text) {
  var regexp = new RegExp('W+'),                  (1)
  var textWithout = text.replaceAll(regexp, ''),
  return textWithout.split(' '),                   (2)
}

We used the RegExp class in line (1), which is more often used to detect pattern matches in a String. Then, we applied the split() method of String in line (2) to produce a list of wordsList. This list is transformed into a Map with the following function:

Map analyzeWordFreq(List wordList) {
  var wordFreqMap = new Map();
  for (var w in wordList) {
    var word = w.trim();
    wordFreqMap.putIfAbsent(word, () => 0);        (3)
    wordFreqMap[word] += 1;
  }
  return wordFreqMap;
}

Note the use of putIfAbsent instead of if...else in line (3).

Then, we will use the generated Map to produce the desired output in the sortWords method:

List sortWords(Map wordFreqMap) {
  var temp = new List<String>();
  wordFreqMap.forEach((k, v) => temp.add('${k}:${v.toString()}'));
  temp.sort();
  return temp;
}

The resulting list is shown in the bottom text area. You can find the complete listing in the word_frequency.dart file.

The Observatory tool

Observatory is the Dart Virtual Machine's (VM) built-in profiling and debugging tool. It is a new class of development tools with a focus on live, immediate reporting of data. Observatory includes tools to profile memory and the CPU usage of Dart programs. Starting with Dart 1.9, Observatory includes a full debugger.

Observatory runs in your browser and allows you to peek inside the running Dart VM on demand while providing real-time reporting on your executing code. For standalone applications, execute your start up <script>.dart dart file from the command line with:

dart --enable-vm-service --pause-isolates-on-start <script>.dart

Then, open your browser with the http://localhost:8181/ URL. The main screen of Observatory will now appear as follows:

The Observatory tool

From this screen, follow the links to more detailed screens:

  • To investigate in which code sections your code spends most of its time, use the cpu profile
  • To take a look at the memory allocation of your data and possible memory fragmentation, use allocation profile and heap map
  • To see which classes are used in your app, start class hierarchy

To start Observatory for a web app, launch chromium from the command line with the HTML start up page (here script.html) as an argument, like this:

<path to chromium>chrome.exe script.html

The command-line output includes a line similar to the following:

Observatory listening on http://127.0.0.1:49621

Open this URL in any browser to bring up the Observatory main screen.

For more detailed information, consult https://www.dartlang.org/tools/observatory/.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.89.2