Learning Dart(Second Edition)
上QQ阅读APP看书,第一时间看更新

A bird's eye view on Dart

It's time to get our feet wet by working on a couple of examples. All the code will be thoroughly explained step by step. Along the way, we will give you a lot of tips and, in the next chapter, we will go into more detail on the different possibilities, thus gaining deeper insight into Dart's design principles.

Example 1 – raising rabbits

Our first real program will calculate the procreation rate of rabbits, which is not only phenomenal but indeed exponential. A female rabbit can have seven litters a year with an average of four baby rabbits each time. So, starting with two rabbits, at the end of the year, you have 2 + 28 = 30 rabbits. If none of the rabbits die and all are fertile, the growth rate will follow the next formula, where n is the number of rabbits after the years specified:

n(years) = 2 x ek x years

Here, the growth factor k = ln(30/2) = ln15. Let's calculate the number after each year for the first 10 years.

Go to File | New Project as before, select Console application, and type the following code, or simply open the script from chapter_1 in the provided code listings (don't worry about the pubspec.yaml file, we'll discuss it in the web version).

The calculation is done in the following prorabbits_v1.dart Dart script:

import 'dart:math';                                      (1)

void main() {
  var n = 0; // number of rabbits                        (2)

  print("The number of rabbits increases as:\n");        (3)
  for (int years = 0; years <= 10; years++) {            (4)
    n = (2 * pow(E, log(15) * years)).round().toInt();   (5)
    print("After $years years:\t $n animals");           (6)
  }
}

Our program produces the following output:

The number of rabbits increases as:

After 0 years:    2 animals
After 1 years:    30 animals
After 2 years:    450 animals
After 3 years:    6750 animals
After 4 years:    101250 animals
After 5 years:    1518750 animals
After 6 years:    22781250 animals
After 7 years:    341718750 animals
After 8 years:    5125781250 animals
After 9 years:    76886718750 animals
After 10 years:  1153300781250 animals

So, if developing programs doesn't make you rich, breeding rabbits will. Because we need some mathematical formulas such as natural logarithms log and power pow, we imported dart:math in line (1). Our number of livestock n is declared in line (2); you can see that we precede its name with var. Here, we don't have to indicate the type of n as int or num (so called type annotations), as Dart uses optional typing.

Note

Local variables are commonly declared untyped as var.

We could have declared it to be of type num (number) or int, because we know that n is a whole number. However, it is not necessary, as Dart will derive this from the context in which n is used. The other number type is called double, used for decimal numbers. Also, the initialization part (= 0) could have been left out. With no initialization var n; or even int n;, it gives n the value null, because every variable in Dart is an object. The null keyword simply indicates that the object has no value yet (meaning that it is not yet allocated in heap memory). It will come as no surprise that // indicates the beginning of a comment, and /* and */ can be used to make a multiline comment.

Tip

Comment on a section of the code by selecting it and then right-click on Toggle comment in the Edit menu.

In the lines (3) and (6), we see that within a quoted string, we can use escape characters such as \n and \t to format our output. Line (4) uses the well-known for loop that is also present in Dart. In order to have the count of animals as a whole number, we needed to apply the round() function. The pow function produces double and because 6750.0 animals doesn't look so good, we have to convert double into int with the toInt() function. In the line (6), the elegant string substitution mechanism (also called string interpolation) is used: print takes a string as argument (a string variable: any expression enclosed within " " or ' ') and, in any such quoted string expression, you can substitute the value of variable n by writing $n. If you want the value of an expression within a string, such as a + b, you have to enclose the expression with braces, for example, ${a + b}.

Tip

You don't have to write ${n} while displaying a variable n, just use $n; you can also simply use print(n).

It is important to realize that we did not have to make any class in our program. Dart is no class junkie like Java or C#. A lot can be done only with functions; but if you want to represent real objects in your programs, classes is the way to go (see the Example 2 – banking section).

Extracting a function

This version of our program is not yet very modular, we would like to extract the calculation in a separate calculateRabbits(years) method that takes the number of years as a parameter. This is shown in the following code (version 2 line (4) of prorabbits_v2.dart) with exactly the same output as version 1:

import 'dart:math';

int rabbitCount = 0;                                   (1)
const int NO_YEARS = 10;                               (2)
const int GROWTH_FACTOR = 15;                          (3)

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

int calculateRabbits(int years) {                      (5)
  return (2 * pow(E, log(GROWTH_FACTOR) * years)).round().toInt();
}

Tip

We could have written this new function ourselves, but Dart has a built-in refactoring called Extract Method. Highlight the line:

n = (2 * pow(E, log(15) * years)).round().toInt();

Right-click and select Extract Method. Dart will do the bulk of the work for you, but we can still simplify the proposed code by omitting the n parameter.

The calculateRabbits function calculates and returns an integer value; this is indicated by the int word preceding the function name. We give the function a return type here (int), but the program would have run without the function-type indication because of Dart's type inference.

This new function is called by main(). This is the way a Dart program works: all the lines in main() are executed in sequence calling functions, as needed, and the execution (and with it, the Dart VM) stops when the } ending of main() is reached. Things get a bit more complicated when Futures or Streams are used: these are put in a microtask queue to be executed. Only when there are no more tasks to execute in this queue will the Dart VM stop executing.

We rename the n variable to rabbitCount, so we need no more comments.

Tip

Renaming a variable is also a built-in refactoring. Select the variable (all the occurrences are then indicated), right-click, and then select Rename.

A good programmer doesn't like hardcoded values such as 10 and 15 in a program. What if they have to be changed? We replace them with constant variables indicated by the const keyword in Dart, whose name is, by convention, typed in capital letters and parts separated by _, see lines (2) and (3).

Tip

Take care of your top-level variables, constants, and functions, because they will probably be visible outside of your program (sometimes, called the interface or API of your program). Type them and name them well.

Now, for some practice:

  1. Examine this second version by going to Tools | Outline.
  2. Set a breakpoint on the rabbitCount = calculateRabbits(years); line by double-clicking on the margin in the front.
  3. Run the program and learn how to use the features of the Debugger tool (press F5 to step line by line, F6 or F7 to step over or out of a function, and F8 to resume execution until the next breakpoint is hit).
  4. Watch the values of the years and rabbitCount variables.

The output should resemble the following screenshot:

Extracting a function

Debugging prorabbits_v2.dart

A web version

As a final version, for now, let us build an app that uses an HTML screen, where we can input the number of years of rabbit elevation and output the resulting number of animals. Go to File | New Project, but this time select Web application. Now, a lot more code is generated, which needs explaining. The app now contains a web subfolder; this will be the home for all of the app's resources. However, for now, it contains a stylesheet (.css file), a hosting web page (.html), and a startup code file (normally main.dart, in our case, prorabbits_v3.dart). The first line in this file makes the HTML functionality available to our code:

import 'dart:html';

We remove the rest of the example code, so only an empty main() function remains. Look at the source of the HTML page right before the </body> tag, it contains the following code:

<script type="application/dart" src="prorabbits_v3.dart"></script>
<script src="packages/browser/dart.js"></script>

The first line starts our Dart script.

The Dart VM exists only in Dartium for testing and debugging purposes. For other browsers, we must supply the Dart-to-JS compiled scripts; this compilation can be done in the Editor by navigating to Tools | Pub Build - Minified Javascript. The output size is minimal: the "dead" JS code that is not used is eliminated in a process called tree shaking. However, where does this mysterious script dart.js come from? The src="packages/browser/dart.js" value means that it is a package available in the Dart repository at http://pub.dartlang.org/.

External packages that your app depends on need to be specified in the dependencies section in the pubspec.yaml file. In our app, it contains (I have changed the name and description) the following:

name: prorabbits_v3
description: Raising rabbits the web way
dependencies:
  browser: any

We see that our app depends on the browser package; any version of it is ok. The package is added to your app when you right-click on the selected pubspec.yaml file and select Pub Get: a packages folder is added to your app and, per package, a subfolder is added containing the downloaded code, in our case, dart.js (In Chapter 2, Getting to Work with Dart, we will explore pub in greater depth and see that it can also be performed from the command-line).

For this program, we replace the HTML <p id="sample_text_id"></p> code as shown in the following code:

<input type="number" id="years" value="5" min="1" max="30">
<input type="button" id="submit" value="Calculate"/>
<br/>Number of rabbits: <label id="output"></label>

The input field with type number (new in HTML5) gives us a NumericUpDown control with a default value 5 limited to the range of 1 to 30. In our Dart code, we now have to handle the click-event at the button with id as submit. We do this in our main() function with the following line of code:

querySelector("#submit").onClick.listen( (e) => calcRabbits() );

The query Selector ("#submit") gives us a reference in the code to the button with ID equal to submit and listen redirects to an anonymous function (see Chapter 2, Getting to Work with Dart) to handle the e event, which calls the calcRabbits()function shown in the following code:

calcRabbits() {
  // binding variables to html elements:
  InputElement yearsInput  = querySelector("#years");        (1)
  LabelElement output = querySelector("#output");            (2)
  // getting input
  String yearsString = yearsInput.value;
  int years = int.parse(yearsString);  
  // calculating and setting output:
  output.innerHtml = "${calculateRabbits(years)}";
}

Here, in the lines (1) and (2), the input field and the output label are bound to the yearsInput and output variables. This is always done in the same way: the querySelector() function takes as its argument a CSS selector; in this case, the ID of the input field (an ID is preceded by a # sign). We typed yearsInput as InputElement (because it is bound to an input field). This way, we can access its value, which is always string. We then convert this string into an int type with the int.parse()function, because calculateRabbits needs an int parameter. The result is shown as HTML in the output label via string substitution, see the following screenshot:

A web version

The screen of prorabbits_v3

All the objects in the Dart code that are bound to the HTML elements are instances of the Element class. Notice how you can change the Dart and HTML code, and save and hit refresh in Dartium (Chrome) to get the latest version of your app.

Example 2: banking

All the variables (strings, numbers, and also functions) in Dart are objects, so they are also instances of a class. The class concept is very important in modeling entities in real-world applications, making our code modular and reusable. We will now demonstrate how to make and use a simple class in Dart by modeling a bank account. The most obvious properties of such an object are the owner of the account, the bank account number, and the balance (the amount of money it contains). We want to be able to deposit or withdraw an amount of money so as to increase or decrease the balance, respectively. This can be coded in a familiar and compact way in Dart, as shown in the following code:

class BankAccount {
  String owner, number;
  double balance;
  // constructor:
  BankAccount(this.owner, this.number, this.balance);    (1)
  // methods:
  deposit(double amount) => balance += amount;           (2)
  withdraw(double amount) => balance -= amount;
}

Notice the elegant constructor syntax in line (1), where the incoming parameter values are automatically assigned to the object fields via this. The methods (line (2)) can also use the shorthand => function syntax, because the body contains only one expression. If you prefer the {} syntax, they will be written as follows:

deposit(double amount) {
  balance += amount;
  return balance;
}

The code in main() makes a BankAccount object ba and exercises its methods (see the banking_v1.dart program):

main() {
  var ba = new BankAccount("John Gates", "075-0623456-72", 1000.0);
  print("Initial balance:\t\t ${ba.balance} \$");
  ba.deposit(250.0);
  print("Balance after deposit:\t\t ${ba.balance} \$");
  ba.withdraw(100.0);
  print("Balance after withdrawal:\t ${ba.balance} \$");
}

The preceding code produces the following output:

Initial balance:               1000.0 $
Balance after deposit:         1250.0 $
Balance after withdrawal:      1150.0 $

Notice how when you type ba. in the editor, the list of the BankAccount class members appears to autocomplete your code. By convention, variables (objects) and functions (or methods) start with a lower case letter and follow the CamelCase notation (http://en.wikipedia.org/wiki/CamelCase), while class names start with a capital letter as well as the word parts in the name. Remember Dart is case sensitive!