Mastering Dart

5 (1 reviews total)
By Sergey Akopkokhyants
  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Beyond Dart's Basics

About this book

Starting with a discussion about the basic features of Dart, we will dive into the more complicated concepts such as generics, annotation with reflection, errors and exceptions, which will help us improve our code. Moving on, you will learn how and when to create objects and also advanced techniques that will help you execute asynchronous code. You will also learn about the collection framework and how to communicate with the different programs written in JavaScript using Dart.

This book will show you how to add internalization support to your web applications and how i18n and l10n access can be embedded into your code to design applications that can be localized easily. You will be shown how to organize client-to-server communication and how different HTML5 features can be used in Dart. Finally, this book will show you how you can store data locally, break the storage limit, and prevent security issues in your web application.

Publication date:
November 2014
Publisher
Packt
Pages
346
ISBN
9781783989560

 

Chapter 1. Beyond Dart's Basics

Dart is a very young computer language with many interesting features. Dart is a class-based, object-oriented language with optional types, and it can help you write very powerful programs. In this chapter, we will cover the following topics:

  • Modularity and a namespace

  • Functions and closures in different scopes

  • Classes and mixins

  • Methods and operators

 

Modularity and a namespace


Complex things are the foundation of our world. To understand the complexity of the things around us, it is necessary to understand the parts that make them up. The evolution of complex things is due to functional and behavioral modularity. Functional modularity is the composition of smaller independent components with clear boundaries and functions. Behavioral modularity is mainly about traits and attributes that can evolve independently.

Modularity is nothing new. Earlier, product manufacturers figured out ways to increase the output and quality of the product, while still managing to reduce the cost pressures. They accomplished this through modularity. Modular design can be seen in automotive industry, buildings, and many other industries. Henry Ford introduced the notion of modularity in his assembly line with standardized and interchangeable parts. As a result, he reduced the production cycles and costs to achieve the mass production of his automobiles. A lot of these concepts are still used by many companies today.

Modularity in software development

Representation of complex things as a set of parts is called decomposition. By analogy, the real-world complex software may be broken into functional parts called modules. Each module can be created, changed, tested, used, and replaced separately.

Let's take a look at the benefits of modularity. For the sake of simplicity, we divide them into development and postproduction phases. Each of these phases has its own specific tasks to be solved in the scope of that phase.

The development phase has the following benefits:

  • Each module requires less code.

  • New features or changes can be introduced to modules in isolation, separate from the other modules.

  • Errors can be easily identified and fixed in a module.

  • Modules can be built and tested independently.

  • Programmers writing the modules can collaborate on the same application.

  • The same modules can be reused in many applications.

  • Applications have a main module and many auxiliary modules. Each module encapsulates a specific functionality and each one is integrated through loosely coupled communication channels provided by the main module.

The postproduction phase has the following benefits:

  • Modules kept in a versioning system can be easily maintained and tested

  • Fixed and noninfrastructural changes in a module can be done without affecting other modules

One significant disadvantage of modularity is that it increases complexity when managing many modules, especially when each one is individually versioned, updated, and has dependencies on the other modules.

Modularity in Dart

The Dart language was designed by keeping the modules in mind. Modularity in Dart is realized through packages, libraries, and classes.

A library exposes functionality as a set of interfaces and hides the implementation from the rest of the world. As a concept, it's very similar to the separation of concern between objects in object-oriented programming (OOP). Separating an application into libraries helps minimize tight coupling and makes it easier to maintain the code. A library can be implemented as a simple function, a single class, several classes, or a collection of parts representing the entire API of a library. The Dart application is a library as well.

A package is simply a directory that contains a pubspec.yaml file and may include any number of libraries and resources. The pubspec.yaml file contains significant information about the package, its authors, and its dependencies on other packages. Here is a sample pubspec.yaml file:

name: animation_library
version: 0.1.0
author: Sergey Akopkokhyants
description: Animation library for Web application
dependencies:
  browser: any

The real pubspec.yaml file can have more fields as specified at https://www.dartlang.org/tools/pub/pubspec.html.Before a package can be used, it must be published to a package management system, which is available as an online resource called pub at https://pub.dartlang.org/. To publish and retrieve packages from pub, we use a utility application of the same name. The pub utility uses information about dependencies from the pubspec.yaml file to retrieve all the necessary packages from the following locations:

Dart Editor manages dependencies automatically for you. You can publish your packages right in Dart Editor.

Libraries

A namespace is a container for all the members of a library. A namespace is defined by the library name. A library that is implicitly named has an empty namespace. This results in a conflict when trying to import libraries with the same namespaces. Import library namespace conflicts can be easily avoided with a prefix clause (as) and a name prefix.

The following is an implicitly named library in which all the resources from dart:html are made available within the scope of our library with the prefix dom:

/**
 * Implicitly named library.
 * The dart:core library is automatically imported.
 */
import 'dart:html' as dom;

/**
 * Get [Element] by [id].
 */
dom.Element getById(String id) => dom.querySelector('#$id');

The library namespace make sense only in the Dart environment.

Note

The code that is compiled in JavaScript loses all the library information.

Dart implements encapsulation through privacy. Each member or identifier of a library has one of the two levels of access: private or public. Private members are visible only inside the library in which they are declared. Conversely, members with a public access are visible everywhere. The difference between them is the underscore prefix (_), as shown in the following code:

// Animation library.
library animation;

// Class publicly available everywhere.
class Animation {
  // ...
}

// Class visible only inside library.
class _AnimationLibrary {
  // ...
}

// Variable publicly available everywhere.
var animationSpeed;

The preceding code shows an animation library with two classes and one variable. The Animation class and the animationSpeed variable are public, and therefore visible outside the library. The _AnimationLibrary class is private and it can be used only in the library.

Public access can be managed with the show and hide extensions of the import statement. Use the following show extension with a specific class, which will then be available inside the library in which it is imported:

import 'animation.dart' as animation show Animation;

// Main entry into Dart Web application.
main() {
  // Animate
  new animation.Animation();
}

The animation prefix in the import statement defines the namespace to import the animation.dart library. All members of the animation.dart library are available in the global namespace via this prefix. We are referring to an Animation class with the animation prefix, as shown here:

Use the hide extension with a specific class, which will then be unavailable inside the library in which it is imported; everything else from the library will be available, as shown in the following code:

import 'animation.dart' as animation hide Animation;

// Main entry into Dart Web application.
main() {
  // Animate
  var speed = animation.animationSpeed;
}

Now we hide the Animation class, but all the other public members in the namespace animation are still available, as seen in the following screenshot:

As you can see, the members of the imported library become invisible. This happens because the library exports members from the public namespace. It can be possible to re-export the imported library with the export statement if this necessary export statement can be managed with show and hide as it was for the import statement, as shown in the following code:

library animation.css;

import 'animation.dart' as animation;
export 'animation.dart' show Animation;

class CssAnimation extends animation.Animation {
  // ...
}

The preceding code shows the animation.css library. We export the Animation class as part of the library namespace. Let's take a look at how we can use them:

There are the exported Animation and original CssAnimation classes available for use in our main code. Without the export, the Animation class would be inaccessible in the main code.

 

Functions and closures in different scopes


I like Dart because everything is an object. Functions are first-class citizens because they support all the operations that are generally available to other types. This means each function have the following properties:

  • They can be named by a variable

  • They can be passed as an argument to a function

  • They can be returned as the result of a function

  • They can be stored in data structures

  • They can be created in any scope

Let's see where and how can we use functions as usual or as first-class citizens.

Naming functions with a variable

Naming functions by variable means that we can create a reference to a function and assign it to a variable, as shown in the following code:

library function_var;

// Returns sum of [a] and [b]
add(a, b) {
  return a + b;
}

// Operation
var operation;

void main() {
  // Assign reference to function [add]
  operation = add;
  // Execute operation
  var result = operation(2, 1);
  print("Result is ${result}");}");
}

Here is the result of the preceding code:

Result is 3

We have the add function and the operation variable. We assign the reference of the add function to a variable and call the variable as a function later.

Passing a function as an argument to another function

Passing functions as arguments to other functions can be very useful in cases when we need to implement the strategy design pattern to enable the program code to be selected and executed at runtime, as shown in the following code:

library function_param;

// Returns sum of [a] and [b]
add(a, b) {
  return a + b;
}

// Operation executor
executor(operation, x, y) {
  return operation(x, y);
}

void main() {
  // Execute operation
  var result = executor(add, 2, 1);
  print("Result is ${result}");
}

Here is the result of the preceding code:

Result is 3

The global executor function from the preceding example can call any function that accepts two arguments. You can see the implementation of the strategy design pattern in the form of anonymous functions passed as parameters of methods in collections.

Returning a function as a result of another function

Sometimes, a function can be returned as a result of another function, as shown in the following code:

library function_return;

// Returns sum of [a] and [b]
add(a, b) => a + b;

// Returns difference between [a] and [b]
sub(a, b) => a - b;

// Choose the function depends on [type]
chooser(bool operation) =>operation ? add : sub;

void main() {
  // Choose function depends on operation type
  var operation = chooser(true);
  // Execute it
  var result = operation(2, 1);
  // Result
  print("Result is ${result}");
}

Here is the result of the preceding code:

Result is 3

This option can be very useful in implementing closures.

Storing a function in data structures

We can store a function in data structures in any collection, as shown in the following code:

library function_store;

// Returns sum of [a] and [b]
add(a, b) => a + b;

// Returns difference between [a] and [b]
sub(a, b) => a - b;

// Choose the function depends on [type]
var operations = [add, sub];

void main() {
  // Choose function from list
  var operation = operations[0];
  // Execute it
  var result = operation(2, 1);
  // Result
  print("Result is ${result}");
}

Here is the result of the preceding code:

Result is 3

We have two functions and the array operations in our example that stores references to them.

Closures

A function can be created in the global scope or within the scope of another function. A function that can be referenced with an access to the variables in its lexical scope is called a closure, as shown in the following code:

library function_closure;

// Function returns closure function.
calculate(base) {
  // Counter store
  var count = 1;
  // Inner function - closure
  return () => print("Value is ${base + count++}");
}

void main() {
  // The outer function returns inner
  var f = calculate(2);
  // Now we call closure
  f();
  f();
}

Here is the result of the preceding code:

Value is 3
Value is 4

We have the calculate function, which contains the count variable and returns a an inner function. The inner function has an access to the count variable because both are defined in the same scope. The count variable exists only within the scope of calculate and would normally disappear when the function exits. This does not happen in this case because the inner function returned by calculate holds a reference to count. The variable has been closed covered, meaning it's within a closure.

Finally, we know what a first-class function is, where we can use them, and how important it is to use closures. Let's move ahead to classes and mixins.

 

Classes and mixins


We all know its wasteful trying to reinvent the wheel. It's even more wasteful trying to do it each time we want to build a car. So how can a program code be written more efficiently and made reusable to help us develop more powerful applications? In most cases, we turn to the OOP paradigm when trying to answer this question. OOP represents the concept of objects with data fields and methods that act on that data. Programs are designed to use objects as instances of classes that interact with each other to organize functionality.

Types

The Dart language is dynamically typed, so we can write programs with or without the type annotations in our code. It's better to use the type annotations for the following reasons:

  • The type annotations enable early error detection. The static analyzer can warn us about the potential problems at the points where you've made the mistakes.

  • Dart automatically converts the type annotations into runtime assertion checks. In the checked mode, the dynamic type assertions are enabled and it can catch some errors when types do not match.

  • The type annotations can improve the performance of the code compiled in JavaScript.

  • They can improve the documentation making it much easier to read the code.

  • They can be useful in special tools and IDE such as the name completion.

The fact that the type annotations were not included in our code does not prevent our program from running. The variables without the type annotations have a dynamic type and are marked with var or dynamic. Here are several recommendations where the type annotations are appropriate:

  • You should add types to public and private variables

  • You can add types to parameters of methods and functions

  • You should avoid adding types to the bodies of methods or functions

Classes

In the real world, we find many individual objects, all of the same kind. There are many cars with the same make and model. Each car was built from the same set of blueprints. All of them contain the same components and each one is an instance of the class of objects known as Car, as shown in the following code:

library car;

// Abstract class [Car] can't be instantiated.
abstract class Car {
  // Color of the car.
String color;
  // Speed of the car.
  double speed;
  // Carrying capacity
  double carrying;

  // Create new [Car] with [color] and [carrying] info.
  Car(this.color, this.carrying);

  // Move car with [speed]
  void move(double speed) {
    this.speed = speed;
  }

  // Stop car.
  void stop() {
    speed = 0.0;
  }
}

Objects have methods and instance variables. The color, speed, and carrying are instance variables. All of them have the value null as they were not initialized. The instance methods move and stop provide the behavior for an object and have access to instance variables and the this keyword. An object may have getters and setters—special methods with the get and set keywords that provide read and write access to the instance variables. The Car class is marked with the abstract modifier, so we can't create an instance of this class, but we can use it to define common characteristics and behaviors for all the subclasses.

Inheritance

Different kinds of objects can have different characteristics that are common with others. Passenger cars, trucks, and buses share the characteristics and behaviors of a car. This means that different kinds of cars inherit the commonly used characteristics and behaviors from the Car class. So, the Car class becomes the superclass for all the different kinds of cars. We allow passenger cars, trucks, and buses to have only one direct superclass. A Car class can have unlimited number of subclasses. In Dart, it is possible to extend from only one class. Every object extends by default from an Object class:

library passenger_car;

import 'car.dart';

// Passenger car with trailer.
class PassengerCar extends Car {
  // Max number of passengers.
  int maxPassengers;

  // Create [PassengerCar] with [color], [carrying] and [maxPassengers].
  PassengerCar(String color, double carrying, this.maxPassengers) :
    super(color, carrying);
}

The PassengerCar class is not an abstract and can be instantiated. It extends the characteristics of the abstract Car class and adds the maxPassengers variable.

Interface

Each Car class defines a set of characteristics and behaviors. All the characteristics and behaviors of a car define its interface—the way it interacts with the outside world. Acceleration pedal, steering wheel, and other things help us interact with the car through its interface. From our perspective, we don't know what really happens when we push the accelerator pedal, we only see the results of our interaction. Classes in Dart implicitly define an interface with the same name as the class. Therefore, you don't need interfaces in Dart as the abstract class serves the same purpose. The Car class implicitly defines an interface as a set of characteristics and behaviors.

If we define a racing car, then we must implement all the characteristics and behaviors of the Car class, but with substantial changes to the engine, suspension, breaks, and so on:

import 'car.dart';
import 'passenger_car.dart';

void main() {
  // Create an instance of passenger car of white color,
  // carrying 750 kg and max passengers 5.
  Car car = new PassengerCar('white', 750.0, 5);
  // Move it
  car.move(100.0);
}

Here, we just created an instance of PassengerCar and assigned it to the car variable without defining any special interfaces.

Mixins

Dart has a mixin-based inheritance, so the class body can be reused in multiple class hierarchies, as shown in the following code:

library trailer;

// The trailer
class Trailer {
  // Access to car's [carrying] info
  double carrying = 0.0;

  // Trailer can carry [weight]
  void carry(double weight) {
    // Car's carrying increases on extra weight.
    carrying += weight;
  }
}

The Trailer class is independent of the Car class, but can increase the carrying weight capacity of the car. We use the with keyword followed by the Trailer class to add mixin to the PassengerCar class in the following code:

library passenger_car;

import 'car.dart';
import 'trailer.dart';

// Passenger car with trailer.
class PassengerCar extends Car with Trailer {
  // Max number of passengers.
  int maxPassengers = 4;

  /**
   * Create [PassengerCar] with [color], [carrying] and [maxPassengers].
   * We can use [Trailer] to carry [extraWeight].
   */
  PassengerCar(String color, double carrying, this.maxPassengers,
      {double extraWeight:0.0}) : super(color, carrying) {
    // We can carry extra weight with [Trailer]
    carry(extraWeight);
  }
}

We added Trailer as a mixin to PassengerCar and, as a result, PassengerCar can now carry more weight. Note that we haven't changed PassengerCar itself, we've only extended its functionality. At the same time, Trailer can be used in conjunction with the Truck or Bus classes. A mixin looks like an interface and is implicitly defined via a class declaration, but has the following restrictions:

  • It has no declared constructor

  • The superclass of a mixin can only be an Object

  • They do not contain calls to super

Well-designed classes

What is the difference between well-designed and poorly-designed classes? Here are the features of a well-designed class:

  • It hides all its implementation details

  • It separates its interface from its implementation through the use of abstract classes

  • It communicates with other classes only through their interfaces

All the preceding properties lead to encapsulation. It plays a significant role in OOP. Encapsulation has the following benefits:

  • Classes can be developed, tested, modified, and used independently

  • Programs can be quickly developed because classes can be developed in parallel

  • Class optimization can be done without affecting other classes

  • Classes can be reused more often because they aren't tightly coupled

  • Success in the development of each class leads to the success of the application

All our preceding examples include public members. Is that right? So what is the rule that we must follow to create well-designed classes?

To be private or not

Let's follow the simple principles to create a well-designed class:

  • Define a minimal public API for the class. Private members of a class are always accessible inside the library scope so don't hesitate to use them.

  • It is not acceptable to change the level of privacy of the member variables from private to public to facilitate testing.

  • Nonfinal instance variables should never be public; otherwise, we give up the ability to limit the values that can be stored in the variable and enforce invariants involving the variable.

  • The final instance variable or static constant should never be public when referring to a mutable object; otherwise, we restrict the ability to take any action when the final variable is modified.

  • It is not acceptable to have the public, static final instance of a collection or else, the getter method returns it; otherwise, we restrict the ability to modify the content of the collection.

The last two principles can be seen in the following example. Let's assume we have a Car class with defined final static list of parts. We can initialize them with Pedal and Wheel, as shown in the following code:

class Car {
  // Be careful with that code !!!
static final List PARTS = ['Pedal', 'Wheel'];
}
void main() {
  print('${Car.PARTS}'); // Print: [Pedal, Wheel]

  // Change part
  Car.PARTS.remove('Wheel');
  print('${Car.PARTS}'); // Print: [Pedal]
}

However, there's a problem here. While we can't change the actual collection variable because it's marked as final, we can still change its contents. To prevent anyone from changing the contents of the collection, we change it from final to constant, as shown in the following code:

class Car {
  // This code is safe
  static const List PARTS = const ['Pedal', 'Wheel'];
}

void main() {
  print('${Car.PARTS}'); // Print: [Pedal, Wheel]

  // Change part
  Car.PARTS.remove('Wheel');
  print('${Car.PARTS}');
}

This code will generate the following exception if we try to change the contents of PARTS:

Unhandled exception:
Unsupported operation: Cannot modify an immutable array
#0 List.remove (dart:core-patch/array.dart:327)
…

Variables versus the accessor methods

In the previous section, we mentioned that nonfinal instance variables should never be public, but is this always right? Here's a situation where a class in our package has a public variable. In our Car class, we have a color field and it is deliberately kept as public, as shown in the following code:

// Is that class correct?
class Car {
  // Color of the car.
  String color;
}

If the Car class is accessible only inside the library, then there is nothing wrong with it having public fields, because they don't break the encapsulation concept of the library.

Inheritance versus composition

We defined the main rules to follow and create a well-designed class. Everything is perfect and we didn't break any rules. Now, it's time to use a well-designed class in our project. First, we will create a new class that extends the current one. However, that could be a problem as inheritance can break encapsulation.

It is always best to use inheritance in the following cases:

  • Inside the library, because we control the implementation and relationship between classes

  • If the class was specifically designed and documented to be extended

It's better not to use inheritance from ordinary classes because it's dangerous. Let's discuss why. For instance, someone developed the following Engine class to start and stop the general purpose engine:

// General purpose Engine
class Engine {
  // Start engine
  void start() {
     // ...
  }

  // Stop engine
  void stop() {
    // ...
  }
}

We inherited the DieselEngine class from the Engine class and defined when to start the engine that we need to initialize inside the init method, as shown in the following code:

import 'engine.dart';

// Diesel Engine
class DieselEngine extends Engine {
  DieselEngine();

  // Initialize engine before start
  void init() {
    // ...
  }
  void start() {
   // Engine must be initialized before use
   init();
   // Start engine
   super.start();
  }
}

Then, suppose someone changed their mind and decided that the implementation Engine must be initialized and added the init method to the Engine class, as follows:

// General purpose Engine
class Engine {
  // Initialize engine before start
  void init() {
    // ...
  }

  // Start engine
  void start() {
    init();
  }

  // Stop engine
  void stop() {
    // ...
  }
}

As a result, the init method in DieselEngine overrides the same method from the Engine superclass. The init method in the superclass is an implementation detail. The implementation details can be changed many times in future from release to release. The DieselEngine class is tightly-coupled with and depends on the implementation details of the Engine superclass. To fix this problem, we can use a different approach, as follows:

import 'engine.dart';

// Diesel Engine
class DieselEngine implements Engine {
  Engine _engine;

  DieselEngine() {
    _engine = new Engine();
  }

  // Initialize engine before start
  void init() {
    // ...
  }

  void start() {
    // Engine must be initialized before use
    init();
    // Start engine
    _engine.start();
  }

  void stop() {
    _engine.stop();
  }
}

We created the private engine variable in our DieselEngine class that references an instance of the Engine class. Engine now becomes a component of DieselEngine. This is called a composition. Each method in DieselEngine calls the corresponding method in the Engine instance. This technique is called forwarding, because we forward the method's call to the instance of the Engine class. As a result, our solution is safe and solid. If a new method is added to Engine, it doesn't break our implementation.

The disadvantages of this approach are associated performance issues and increased memory usage.

 

Methods and operators


Now that we've introduced well-designed classes, we need to discuss methods.

Checking the values of the parameters before using them

The class constructors, methods, mutators (setters), and operators remove some restrictions on the values that must be passed into their parameters .What will happen if an invalid parameter value is passed to a method? One possibility is that the method will fail with a confusing exception or worse it will succeed but with a wrong result. In any case, it's dangerous not check the parameters of a method before using them. The rule here is to check whether the parameter value is valid as soon as possible. The best place to do that is at the beginning of the method.

The Dart VM can work in a developer-friendly checked mode and a speed-obsessed production mode. We usually use the checked mode when developing our applications. One of the benefits of this mode is the dynamic assertion. We should use the assert statement to check whether the parameters of the method are valid before using it. The Dart VM continues the program execution if the Boolean result of the dynamic assertion is true, otherwise stops it. This is shown in the following code:

/**
 * Return sum of [a] and [b].
 * It throws [AssertionError] if any of [a] or [b] equals null
 */
sum(int a, int b) {
  assert(a != null);
  assert(b != null);
  return a + b;
}

Note

The assert statement has no effect when the program executes in the production mode or is compiled with the JavaScript code.

We must check the validity of the parameters stored in the method for later use. Ignoring this can lead to problems later because an error associated with the parameter can be thrown in a completely different place, making it harder to trace its source. This has serious implications, especially in constructors.

Sometimes, it is important to validate the internal state of a class in the method and generate a special error, as shown in the following code. The typical errors are StateError, RangeError, and ArgumentError.

class Car {
  double petrol;

  /**
   * Start engine.
   * That method throws [StateError] if petrol is null
   * or less than 5 liters.
   */
  void startEngine() {
    if (petrol == null || petrol <= 5.0) {
      throw new StateError('Not enough petrol');
    }
  }
}

Here, we have a Car class with the petrol variable and the startEngine method. The startEngine method checks whether there is enough petrol to start the engine; otherwise, it throws an error.

Note

Each time you create a method, think about the restrictions that apply to its parameters.

Well-designed methods

So, now that we've defined well-designed classes, it's time to define well-designed methods. We must remember that methods are part of a class' interface and the following simple rules can make them easier to use and also less error-prone:

  • Choose the right method name. Remember, Dart doesn't support method overloading. Instead, we can have different method names or optional parameters.

  • Use optional named parameters. This helps programmers to use your methods without the need to remember the position of each parameter.

  • Refer to objects in terms of their interfaces over classes as the type of parameters. For example, we have an interface and the class implements that interface. Use the interface as the parameter type of the method instead of a solid one. Don't restrict the solution to a particular implementation.

A car may have the following different types of engines:

// Engine interface
abstract class Engine {
  void start();
}

// Diesel engine
class DieselEngine implements Engine {
  void start() {
    // ...
  }
}

// Carburetor engine
class CarburetorEngine implements Engine {
  void start() {
    // ...
  }
}

// Car
class Car {
  var engine;

  // Car may have any engine
  Car(Engine this.engine);
}

It's better to pass the abstract Engine class as a parameter of the constructor for the car to prevent any problems in future.

Tip

Downloading the example code

You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.

 

Summary


This chapter covered some of the most useful advanced features of the Dart language. The Dart language was designed with the modules in mind. Modularity in Dart is realized through packages, libraries, and classes. The code compiled in JavaScript loses all the library information.

Functions are first-class citizens because they support all the operations generally available to other types. A function that can be referenced with an access to the variables in its lexical scope is called a closure.

Programs are designed to use objects as instances of classes that interact with each other to organize functionality. The Dart language is dynamically typed, so we can write programs with or without the type annotations in our code.

In the next chapter, we will talk about generics, errors and exceptions, and annotations and reflection.

About the Author

  • Sergey Akopkokhyants

    Sergey Akopkokhyants is a software architect with more than 20 years of professional experience in designing and developing client and server-side applications. He is also a certified Java developer and project manager. He has general knowledge of many tools, languages, and platforms.

    For the last decade, Sergey has been responsible for customizing and producing weboriented applications for wholesale business management solutions projects for several worldwide mobile communication companies. His responsibilities have included: architecture design and guidance of client software development using Flex, CSS, HTML, JavaScript, TypeScript, and Dart, and client-server integration with Java. He is also the founder and an active contributor to several open source projects on GitHub.

    Sergey is passionate about web design and development and likes sharing his expertise with others, helping them to increase their skills and productivity. He is author of the book Mastering Dart and also he was one of reviewers of the books Learning Dart and Dart Cookbook.

    Browse publications by this author

Latest Reviews

(1 reviews total)
This book is the best of all ... all it needs is a second edition to pull it up to spec with modern Dart SDK !