I recently wrote an article about Angular, and the differences between writing an Angular app in Dart and in TypeScript.
I managed to find some good stuff related to how to write TypeScript code if you want to write an Angular app, but I noticed there were no tutorials for Dart, so I decided to write one!
Prerequisites
Before beginning this tutorial, you should install Dart for the operating system that you need. If you don't want to install Dart, try using DartPad. It's like Ellie or JsFiddle but for Dart.
For this tutorial, I'm assuming that you will run Dart code in checked mode. If I have a dart file named hello.dart
for example, on the command line I would run my code like this:
$ dart hello.dart -c
"Checked" or "strong" mode in Dart enforces type-checking when compiling and running code. This will be the standard in Dart going forward, so it's best to get a handle for strong mode now rather than later.
hello world!
If you have learned any programming language, you might find Dart's hello world program to be very similar to many other languages:
void main() {
print('hello world!');
}
In this example, I'm defining a function main
that returns void
(nothing), and it calls a print
function that prints "hello world!" out to the console!
Dart makes it possible for this to be a one-liner using the power of closures (read here for more info):
void main() => print('hello world!');
Dart Basics
Dart has support for seven different built-in types. It's really easy to use them too:
void main() {
String name = 'Bob'; // text
String apple = '\u{1F34E}'; // one character
String orange = '\u{1F34A}';
int numApples = 5; // integer value
double numOranges = 1.5; // floating point value
print('$name has $numApples $apple and $numOranges $orange');
bool hasLots = (numApples + numOranges) > 5.0;
if(hasLots) {
print('$name has a lot of fruits!!');
} else {
print('$name needs more fruits...');
}
}
In this example there's a lot going on. I defined a String
with the name name
and a value of "Bob"
. I also have a couple of unicode characters defined, the numbers of each of the fruits that Bob has, and I'm using string interpolation to print out all of the information.
By prefixing $
to the name of a variable and putting it directly into a string, I can make Dart print what I want it to print without needing to concatenate the string myself.
I'm also using mathematical operators to add two numbers together. There are a number of operators you can use.
Dart also has flow control statements which allow you to have branching behavior in your code. Statements like if
and else
can make your code perform differently depending on the state of different variables. In this example, hasLots
evaluates as true
, so the print
statement that falls within the if
block is executed, and not the one in the else
block.
Things to try: change name
to your own name. Try changing the numbers to where they don't add up to 5. Change either apple
or orange
to "\u{1F953}"
and notice how inaccurate the print statements become. Change each type to var
and notice that nothing changes.
Defining Your Own Types
Let's say that you want to design classes that can help you represent shapes. We want to be able to calculate the area and perimeter of two-dimensional shapes.
In Dart, you can start by defining what you want your behaviors to be in the form of a class:
class Shape {
double area() => 0.0;
double perimeter() => 0.0;
}
This is a pretty dumb shape though... It doesn't really do anything. All of the behaviors are zeroed out. In Dart, this is how you would create an interface.
Dart doesn't have a special keyword for interfaces. You essentially just create default behavior in a class and then you @override
it in extending classes.
For example, if I wanted to write a class for circles and rectangles, I could write something like this:
class Circle implements Shape {
final double _radius;
Circle(this._radius); // Circle constructor
@override
double area() => PI * pow(this._radius, 2);
@override
double perimeter() => 2 * (PI * this._radius);
}
class Rectangle implements Shape {
final double _height, _width;
Rectangle(this._height, this._width); // Rectangle constructor
Rectangle.square(double size) : this(size, size); // alternate constructor
@override
double area() => this._height * this._width;
@override
double perimeter() => (this._height * 2) + (this._width * 2);
}
There's a lot going on here: Circle
and Rectangle
both implements Shape
, meaning that instances of those classes are Shape
s. They each have an area()
and perimeter()
method, but they are created differently:
void main() {
Shape circle = new Circle(5.0);
Shape rectangle = new Rectangle(4.0, 3.0);
Shape square = new Rectangle.square(3.3);
List<Shape> shapes = [circle, rectangle, square];
shapes.forEach((shape) {
print("This shape has an area of ${shape.area()} and a perimeter of ${shape.perimeter()}");
});
}
In this example, the Rectangle
class has two constructors: the default that takes in a height
and a width
, and then another called square
that sets the height
and the width
of the Rectangle
to the same value.
While the square
constructor looks as if it's being invoked as a function, it's not really a function. You still have to use the new
keyword because you're still instantiating an object, but it's not a function so it can't be invoked as a function. If you were to remove the new
keyword, that would cause a compile-time error.
Wrapping up
I hope this intro was good! If you want to know more about Dart, the language tour is going to be the most helpful resource you have. If you want me to get more in-depth about something, let me know in the comments, or ask an #explainlikeimfive question and maybe I can help :)
Top comments (3)
Wouldn't be this the correct way to write an interface in dart?
When you write an interface like the on below, should we use the '@override' decorator in the implementation? And if we use the @override decorator, is it only for make our intention clear when we are reading the code? because the code works with or without the @override decorator when we write an interface like the one below.
Traditionally in languages that have OO paradigms, there is some sort of keyword, decorator, or annotation that makes the intent clear that the behavior being defined comes from some other kind of class or interface.
@override
might not be required, but it makes the intent clearer to others who are looking at your code. That, and if the classes or interfaces it extends from change, this would result in a compiler error since that method isn't truly being overriden anymore.To your point about interfaces, I think it's perfectly valid to define an interface either way. I would almost say that adding the abstract keyword makes it more like an interface since in my example someone could still construct a
Shape
whereas in your example they can't.The only thing I can think of that wouldn't make either of our examples pure interfaces is if the
Shape
class also included member variables and getters and setters. That would be more along the lines of a traditional abstract class, but is less favorable in OO languages due to the complex nature of extending classes.I tried looking in Effective Dart for a more official answer but I couldn't find one ☹️