A bird's eye view on Dart
It's time to get our feet wet by working on a couple of examples. All 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 follows the following formula, where n is the number of rabbits after the years specified:
Here the growth factor k = ln(30/2) = ln15. Let us calculate the number after each year for the first 10 years.
Go to File | New Application as before, select Command-line application and type the following code, or simply open the script from chapter_1
in the provided code listings. (Don't worry about the file pubspec.yaml
; we'll discuss it in the web version.)
The calculation is done in the following Dart script prorabbits_v1.dart
:
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.
We could have declared it to be of type num
(number) or int
, because we know that n
is a whole number. But this is not necessary as Dart will derive that from the context in which n
is used. The other num 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;
gives n
the value null
, because every variable in Dart is an object. The keyword null
simply indicates that the object has no value yet (meaning 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 multi-line comment.
In 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 a double
and because 6750.0 animals doesn't look so good, we have to convert the double
to an int
with the toInt()
function. In 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}
.
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).
This version of our program is not yet very modular; we would like to extract the calculation in a separate method calculateRabbits(years)
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(); }
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();
The calculateRabbits
function calculates and returns an integer value; this is indicated by the word int
preceding the function name. We give the function a type here because it is top level, but the program would have run without the function-type indication.
This new function is called by main()
. This is the way a Dart program works: all 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. We rename the variable n
to rabbitCount
, so we need no more comments.
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 with keyword const
in Dart, whose name is, by convention, typed in capital letters and parts separated by _
, see lines (2
) and (3
).
And now for some practice:
- Examine this second version by going to Tools | Outline.
- Set a breakpoint on the line
rabbitCount = calculateRabbits(years);
by double-clicking in the margin in front. - 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).
- Watch the values of the
years
andrabbitCount
variables.
The output should resemble the following screenshot:
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 Application, but this time select Web application. Now a lot more code is generated that needs explaining. The app now contains a subfolder web
; this will be the home for all of the app's resources, but for now it contains a stylesheet (.css
file), a hosting web page (.html
), and a startup code file (in our case prorabbits_v3.dart
). The first line in this file makes 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 is evident: our Dart script must be started. But wait, how do we know that there is a Dart VM available in this browser? This will be checked in the second JavaScript file, dart.js
; the first few lines of code in this file are:
if (navigator.webkitStartDart) { // Dart VM is available, start it! } else { // Fall back to compiled JavaScript }
The Dart VM exists for the moment only in Dartium (soon in Chrome). For other browsers we must supply the Dart-to-JS compiled scripts; this compilation can be done in the Editor by navigating to Tools | Generate Javascript. The output size is minimal: dead js code that is not used is eliminated in a process called tree shaking. But where does this mysterious script dart.js
come from? src="packages/browser/dart.js"
means that it is a package available in the Dart repository http://pub.dartlang.org/.
External packages that your app depends on need to be specified in the section, dependencies
, in the file pubspec.yaml
. In our app this section contains the following parameters:
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
and select Pub Get: a folder packages
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.)
For this program we replace the HTML <p id="sample_text_id"></p>
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 and limited to the range 1 to 30. In our Dart code, we now have to handle the click-event on the button with id
as submit
. We do this in our main()
function with the following line of code:
query Selector ("#submit").onClick.listen( (e) => calcRabbits() );
query Selector ("#submit")
gives us a reference in the code to the button, listen redirects to an anonymous function (see Chapter 2, Getting to Work with Dart) to handle this event e
, which calls the function calcRabbits()
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 lines (1
) and (2
), the input field and the output label are bound to the variables in_years
and output
. This is always done in the same way: the query Selector()
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 in_years
as an InputElement
(because it is bound to an input field), that way we can access its value, which is always a string
. We then convert this string
to an int
type with the function int.parse()
, because calculateRabbits
needs an int
parameter. The result is shown as HTML in the output label via string substitution, see the following screenshot:
All objects in Dart code that are bound to HTML elements are instances of the class Element
. Notice how you can change the Dart and HTML code; save and hit refresh in Dartium (Chrome) to get the latest version of your app.
Example 2 – banking
All 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 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 an amount of money in it that increases the balance, or withdrawing an amount so as to decrease the balance. 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; }
The code in main()
makes a BankAccount
object ba
and exercises its methods (see program banking_v1.dart
):
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 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!