Dart is the programming language for Flutter, a mobile app SDK from Google. This codelab introduces you to Dart, with a focus on features that Java developers might not expect.
You can be writing Dart functions in 1 minute, scripts in 5 minutes, and apps in 10 minutes!
To complete this codelab, all you need is a browser!
You'll write and run all the examples in DartPad, an interactive, browser-based tool that lets you play with Dart language features and core libraries. If you prefer, you can use an IDE instead, such as WebStorm, IntelliJ with the Dart plugin, or Visual Studio Code with the Dart Code extension.
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.
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.
Note that DartPad immediately runs its code.
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
main()
or (if you need access to command line arguments) main(List<String> args)
.main()
method lives at the top level. In Dart, you can define code outside of classes. Variables, functions, getters, and setters can all live outside of classes.private
tag, which Dart doesn't use. You'll learn more about privacy in "Step 3: Add a read-only variable".main()
nor Bicycle is declared as public
, because all identifiers are public by default. Dart doesn't have keywords for public
, private
, or protected
.Add the following constructor to the Bicycle class:
Bicycle(this.cadence, this.speed, this.gear);
Observations
;
) at the end of a no-body constructor, DartPad displays an error: "A function body must be provided."this
in a constructor's parameter list is a handy shortcut for assigning values to instance variables.Bicycle(int cadence, int speed, int gear) {
this.cadence = cadence;
this.speed = speed;
this.gear = gear;
}
Reformat the Dart code at any time by clicking Format at the top of the DartPad UI. Reformatting is particularly useful when you've pasted code into DartPad and the justification is off.
Click Format.
Add the following code to the main()
function:
void main() {
var bike = new Bicycle(2, 0, 1);
print(bike);
}
Remove the optional new
keyword:
var bike = Bicycle(2, 0, 1);
Observation
new
keyword became optional in Dart 2.final
instead of var
.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
var bike = ...
defines a Bicycle instance.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
@override
annotation tells the analyzer that you are intentionally overriding a member. The analyzer raises an error if you've failed to perform the override properly.${expression}
. If the expression is an identifier, you can skip the braces: $variableName
.=>
) notation.Click Run.
You should now see the following output:
Bicycle: 0 mph
Problems?
Check your code
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
In the Bicycle constructor, remove the speed
parameter:
Bicycle(this.cadence, this.gear);
In main(), remove the second (speed) parameter from the call to the Bicycle constructor:
var bike = Bicycle(2, 1);
Change the remaining occurrences of speed
to _speed
. (2 places)
Initialize _speed
to 0:
int _speed = 0;
Add the following getter to the Bicycle class:
int get speed => _speed;
Observations
null
.cadence
and gear
in the original Java example, they aren't, by definition, considered private in the Dartiverse. Those instance variables can be accessed using bike.gear
or bike.cadence
.bike.cadence
, and later refactor it to use getters and setters. The API stays the same. In other words, going from a field to a getter/setter is not a breaking change in Dart.Finish implementing speed as a read only instance variable
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 23 lines instead of 40:
class Bicycle {
int cadence;
int _speed = 0;
int get speed => _speed;
int gear;
Bicycle(this.cadence, this.gear);
void applyBrake(int decrement) {
_speed -= decrement;
}
void speedUp(int increment) {
_speed += increment;
}
@override
String toString() => 'Bicycle: $_speed mph';
}
void main() {
var bike = Bicycle(2, 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 or type 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
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
this.origin
, this.width
, and this.height
use the shorthand trick for assigning instance variables inside a constructor's declaration.this.origin
, this.width
, and this.height
are optional named parameters. Named parameters are enclosed in curly braces ({}
).this.origin = const Point(0, 0)
syntax specifies a default value of Point(0,0)
for the origin
instance variable. The specified default must be a compile-time constant. This constructor supplies default values for all three instance variables. Add the following toString()
function to the Rectangle class:
@override
String toString() =>
'Origin: (${origin.x}, ${origin.y}), width: $width, height: $height';
Replace main()
with the following code to verify that you can instantiate Rectangle using only the parameters you need.
main() {
print(Rectangle(origin: const Point(10, 20), width: 100, height: 200));
print(Rectangle(origin: const Point(10, 10)));
print(Rectangle(width: 200));
print(Rectangle());
}
Observation
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 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() {
final circle = Circle(2);
final square = Square(2);
print(circle.area);
print(square.area);
}
Open the Shapes example in DartPad
In the console area, you should see the computed areas of a circle and a square:
12.566370614359172
4
Observations
PI
); in Dart 2, they're lowercase (pi
).num get area => pi * pow(radius, 2); // Circle
num get area => pow(side, 2); // Square
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 Circle(2);
if (type == 'square') return Square(2);
throw 'Can\'t create $type.';
}
Invoke the factory function by replacing the first two lines in the main()
method:
final circle = shapeFactory('circle');
final square = shapeFactory('square');
The output should look the same as before.
Observations
'circle'
or 'square'
, it throws an exception.Uncaught
. To see information that's more helpful, wrap the code in a try-catch
statement, and print the exception. As an optional exercise, check out this DartPad example.'Can\'t create $type.'
) or specify the string with double quotes ("Can't create $type."
).Problems?
Check your code
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 Circle(2);
if (type == 'square') return Square(2);
throw 'Can\'t create $type.';
}
num get area;
}
Replace the first two lines of main() with the following code for instantiating the shapes:
final circle = Shape('circle');
final square = Shape('square');
Delete the shapeFactory()
function that you added previously.
Observation
shapeFactory()
function.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)
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, as in this example.
The following example uses imperative (not functional-style) code:
String scream(int length) => "A${'a' * length}h!";
main() {
final values = [1, 2, 3, 5, 10, 50];
for (var length in values) {
print(scream(length));
}
}
Open the Scream example in DartPad
The output should look like the following:
Aah!
Aaah!
Aaaah!
Aaaaaah!
Aaaaaaaaaaah!
Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah!
Observation
${'a' * length}
evaluates to "the character 'a'
repeated length
times." 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);
The functional approach prints the same six "screams" as the imperative example.
Problems?
Check your code
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);
The output should look like the following:
Aaah!
Aaaah!
Aaaaaah!
Observations
skip(1)
skips the first value, 1, in the values
list literal.take(3)
gets the next 3 values—2, 3, and 5—in the values
list literal.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 increase 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.
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'd like to see Dart technologies in action, try the Flutter codelabs.
You can learn much more about Dart with the following articles, resources, and websites.
Articles
Resources
Websites