DEV Community

Jake Varness
Jake Varness

Posted on

Dart for Beginners

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
Enter fullscreen mode Exit fullscreen mode

"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!');
}
Enter fullscreen mode Exit fullscreen mode

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!');
Enter fullscreen mode Exit fullscreen mode

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...');
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

There's a lot going on here: Circle and Rectangle both implements Shape, meaning that instances of those classes are Shapes. 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()}");
  });
}
Enter fullscreen mode Exit fullscreen mode

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 (4)

Collapse
 
ram535 profile image
aldama • Edited

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.

abstract class Shape {
  double area();
  double perimeter();
}
Collapse
 
jvarness profile image
Jake Varness

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.

Collapse
 
jvarness profile image
Jake Varness

I tried looking in Effective Dart for a more official answer but I couldn't find one ☹️