Dart is the programming language for Flutter, a new mobile app SDK from Google. This codelab covers a few of the ways in which Dart makes it easy and fun to write modern apps. Hopefully, you will find, as others Java programmers have reported, that Dart feels familiar and is quick to learn.

You can be writing Dart functions in 1 minute, scripts in 5 minutes, and apps in 10 minutes!

What you'll learn

What you'll need

All you need is a browser!
You will run all the examples in DartPad, an interactive, browser-based tool that lets you play with Dart language features using the core libraries (except for dart:io). If you prefer, you can use an IDE, such as IntelliJ or WebStorm.

Why are you interested in Dart?

I want to write Flutter apps I want to write web apps using AngularDart I want to write servers I have a general interest in the Dart language

You'll start by building a simple Dart class with the same functionality as the Bicycle class from the Java Tutorial. The Bicycle class contains some private instance variables with getters and setters. A main() method instantiates a Bicycle and prints it to the console.

Launch DartPad

This codelab provides a new DartPad instance for every set of exercises. The link below opens a fresh instance, which contains a default ‘Hello' example. You can continue to use the same DartPad throughout the codelab, but if you click the Reset button, DartPad takes you back to the default example, losing your work.

For your convenience, at the top of each codelab page is a DartPad instance that reflects the state at the beginning of each exercise.

Open DartPad

Note that DartPad immediately runs its code.

Enable strong mode

Dart is a type safe language. With strong mode enabled, its type system is also sound. That is, static type annotations in Dart code that is strong mode compliant are guaranteed to be correct at runtime.

Click the strong mode checkbox in the lower right to enable type checking.

Define a Bicycle class

Above the main() function, add a Bicycle class with three instance variables. Also remove the contents from main(), as shown below:

class Bicycle {
  int cadence;
  int speed;
  int gear;
}

void main() {
}

Observations

Define a Bicycle constructor

Add the following constructor to the Bicycle class:

Bicycle(this.cadence, this.speed, this.gear);

Observations

Reformat the code

Reformat the Dart code at any time by clicking Reformat at the top left, next to the Reset button. This is particularly useful when you've pasted code into DartPad and the justification is off.

Click Reformat.

Instantiate and print a bicycle instance

Add the following code to the main() function:

void main() {
  var bike = new Bicycle(2, 0, 1);
  print(bike);
}

Run the example

Execute the example by clicking the enabled Run button at the top of the DartPad window. If Run is not enabled, see the Problems section below.

You should see the following output:

Instance of 'Bicycle'

Observation

Improve the output

While the output "Instance of Bicycle" is correct, it's not very informative. All Dart classes have a toString() method that you can override to provide more useful output.

Add the following toString() method anywhere in the Bicycle class:

@override
String toString() => 'Bicycle: $speed mph';

Observations

Run the example

Click Run.

You should now see the following output:

Bicycle: 0 mph

Problems?
Check your code

Add a read-only variable

The original Java example defines speed as a read-only variable—it declares it as private and provides only a getter. Next, you'll provide the same functionality in Dart.

Open bicycle.dart in DartPad (or continue using your copy)

To mark a Dart identifier as private, start its name with an underscore (_). You can convert speed to read-only by changing its name and adding a getter.

Make speed a private, read-only instance variable

Change speed to _speed. (3 places)

Tip: If you are using a JetBrains IDE, you can simultaneously rename all instances of a variable by right-clicking the name, and choosing Refactor > Rename... from the popup menu.

Add the following getter to the Bicycle class:

int get speed => _speed;

Observations

Finish implementing speed as a read only instance variable

For completeness' sake, add the following methods to the Bicycle class:

void applyBrake(int decrement) {
  _speed -= decrement;
}

void speedUp(int increment) {
  _speed += increment;
}

The final Dart example looks similar to the original Java but is more compact at 25 lines instead of 45:

class Bicycle {
  int cadence;
  int _speed;
  int gear;

  Bicycle(this.cadence, this._speed, this.gear);

  int get speed => _speed;

  void applyBrake(int decrement) {
    _speed -= decrement;
  }

  void speedUp(int increment) {
    _speed += increment;
  }

  @override
  String toString() => 'Bicycle: $_speed mph';
}

main() {
  var bike = new Bicycle(2, 0, 1);
  print(bike);
}

Problems?
Check your code

The next exercise defines a Rectangle class, another example from the Java Tutorial.

The Java code shows overloading constructors, a common practice in Java where constructors have the same name, but differ in the number of parameters and/or the types of parameters. Dart doesn't support overloading constructors and handles this situation differently, as you will see in this section.

Open the Rectangle example in DartPad

Enable strong mode

Click the strong mode checkbox.

Add a Rectangle constructor

Add a single, empty constructor that replaces all four constructors in the Java example:

Rectangle({this.origin = const Point(0, 0), this.width = 0, this.height = 0});

This constructor uses optional named parameters.

Observations

Improve the output

Add the following toString() function to the Bicycle class:

@override
String toString() =>
      'Origin: (${origin.x}, ${origin.y}), width: $width, height: $height';

Use the constructor

Replace main() with the following code to verify that you can instantiate Rectangle using only the parameters you need.

main() {
  print(new Rectangle(origin: const Point(10, 20), width: 100, height: 200));
  print(new Rectangle(origin: const Point(10, 10)));
  print(new Rectangle(width: 200));
  print(new Rectangle());
}

Observation

Run the example

You should see the following output:

Origin: (10, 20), width: 100, height: 200
Origin: (10, 10), width: 0, height: 0
Origin: (0, 0), width: 200, height: 0
Origin: (0, 0), width: 0, height: 0

Problems?
Check your code

Factories, a commonly used design pattern in Java, have several advantages over direct object instantiation, such as hiding the details of instantiation, providing the ability to return a subtype of the factory's return type, and optionally returning an existing object rather than a new object.

This step demonstrates two ways to implement a shape-creation factory:

For the this exercise, you'll use the Shapes example, which instantiates shapes and prints their computed area:

import 'dart:math';

abstract class Shape {
  num get area;
}

class Circle implements Shape {
  final num radius;
  Circle(this.radius);
  num get area => PI * pow(radius, 2);
}

class Square implements Shape {
  final num side;
  Square(this.side);
  num get area => pow(side, 2);
}

main() {
  var circle = new Circle(2);
  var square = new Square(2);
  print(circle.area);
  print(square.area);
}

Open the Shapes example in DartPad

Enable strong mode

Click the strong mode checkbox.

Run the example

After execution, you should see the computed areas of a circle and a square:

12.566370614359172
4

Observations

Option 1: Create a top-level function

Implement a factory as a top-level function by adding the following function at the highest level (outside of any class):

Shape shapeFactory(String type) {
  if (type == 'circle') return new Circle(2);
  if (type == 'square') return new Square(2);
  throw 'Can\'t create $type.';
}

Invoke the factory function by replacing the first two lines in the main() method:

  var circle = shapeFactory('circle');
  var square = shapeFactory('square');

Run the example

The output should look the same as before.

Observations

Problems?
Check your code

Option 2: Create a factory constructor

Use Dart's factory keyword to create a factory constructor.

Add a factory constructor to the abstract Shape class

abstract class Shape {
  factory Shape(String type) {
    if (type == 'circle') return new Circle(2);
    if (type == 'square') return new Square(2);
    throw 'Can\'t create $type.';
  }
  num get area;
}

Replace the first two lines of main() with the original code for instantiating the shapes:

  var circle = new Shape('circle');
  var square = new Shape('square');

Delete the shapeFactory() function added previously.

Observation

Problems?
Check your code

The Dart language doesn't include an interface keyword, because every class defines an interface.

Open the Shapes example in DartPad (or continue using your copy)

Enable strong mode

Click the strong mode checkbox.

Add a CircleMock class that extends the Circle class:

class CircleMock implements Circle {}

You should see a "Missing concrete implementations" error. Fix this error by defining the area and radius instance variables:

class CircleMock implements Circle {
  num area;
  num radius;
}

Observation

Problems?
Check your code

In functional programming you can do things like:

Dart supports all of these features. In Dart, even functions are objects and have a type, Function. This means that functions can be assigned to variables or passed as arguments to other functions. You can also call an instance of a Dart class as if it were a function.

The following example uses imperative (non-functional) code:

String scream(int length) {
  return "A${'a' * length}h!";
}

main() {
  var values = [1, 2, 3, 5, 10, 50];
  for (var length in values) {
    print(scream(length));
  }
}

Open the Scream example in DartPad

Enable strong mode

Click the strong mode checkbox.

The output should look like the following:

Aah!
Aaah!
Aaaah!
Aaaaaah!
Aaaaaaaaaaah!
Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah!

Observation

Convert imperative code to functional

Remove the imperative for() {...} loop in main() and replace it with a single line of code that uses method chaining:

  values.map(scream).forEach(print);

Run the example

The functional approach prints the same six "screams" as the imperative example.

Problems?
Check your code

Leveraging Iterables

Lists and Iterables, from dart:collection, support fold, where, join, skip, and more. Dart also has Maps and Sets.

Replace the values.map() line in main() with the following:

  values.skip(1).take(3).map(scream).forEach(print);

Run the example

The output should look like the following:

Aaah!
Aaaah!
Aaaaaah!

Observations

Problems?
Check your code

In completing this codelab, you've gained knowledge on some differences between Java and Dart. Dart is easy to learn and, in addition, its core libraries and rich set of available packages increases your productivity. Dart scales well to large applications. Hundreds of Google engineers use Dart to write mission critical apps that bring in much of Google's revenue.

Next steps

A 20-minute codelab isn't long enough to show you all of the differences between Java and Dart. For example, this codelab hasn't covered:

If you are interested in seeing Dart technologies in action, try these codelabs:

You can learn much more about Dart with the following articles, resources, and websites.

Articles

Resources

Websites