Home Tutorials Training Consulting Products Books Company Donate Contact us









Get more...

Training Events

This tutorial gives a basic overview over the programming language Dart

1. Dart

The Dart programming language is a language developed by Google. It is widely used at Google and can be used for:

  • mobile app development using Flutter

  • web development (using Dart2JS)

  • server side (using various libraries and frameworks)

The syntax of Dart is very close to Java and C++. Therefore, the object oriented language is very approachable for developers with knowledge in other programming languages.

Dart uses the following tools

  • stagehand - provide templates for creating Dart apps

  • pub - package manager for Dart == Installation

To be able to run or compile Dart apps you will need to have the Dart SDK installed. The official documentation is very good and can be used for this step https://dart.dev/get-dart.

The SDK can be downloaded from the Dart installation website.

Follow the instructions for your operating system.

After that the Dart SDK should be automatically added to your path.

To test the availability of dart, type dart --version in a terminal.

Linux

On Linux Dart is typically installed in /usr/lib/dart/bin. To add it to your path, simply add export $PATH:/usr/lib/dart/bin at the end of the .bashrc file in your home directory.

MacOS

Add /Applications/dart/dart-sdk/bin at the end of /etc/paths.

1.1. Using additional modules

Dart comes with some other modules that may need to be added to the path.

One of them is the Dart Package Manager "Pub". Having it on the path can be handy as it allows you to use packages from the pub registry, similar to NPM.

To make all of the extra tools available anywhere, add the folder <dart-location>/bin to your path.

On most Linux distributions and macOS you can find the <dart-location> by typing which dart into a terminal.

2. Development

Dart execution can be triggered from the command line. To develop and run a Dart application, you therefore only need the Dart SDK and a text editor.

However it is recommended to use an IDE as it can help with more complex tasks. Such tools are:

2.1. Running a Dart application

Running a Dart program requires a file with a main() method. This file can then be executed via the following command line:

$ dart path-to-file.dart.

The compiler will automatically pick up the main() method and execute it.

2.2. Packaging / Building for production

To bundle an application Dart uses so called "snapshots". It bundles all files in your project into an executable binary file. The Dart SDK can then execute the file.

Creating a snapshot:

$ dart --snapshot=<your-snapshot-name> <your-main-file>

Then run the created snapshot:

$ dart <your-snapshot-name>

2.3. Running a Dart App in the Browser

Dart can be run in the browser. For this it needs to be transpiled to JavaScript first.

There is a tool available to handle development and build, as well as optimizations for you. It is called webdev and is a standalone tool that can be run on the command line.

2.4. Dart on Mobile

Dart is the official language of the Flutter project. It enables Dart apps to be run on both Android and iOS. See our Flutter tutorial for a detailed introduction into the framework.

3. Exercise - Developing Server and Frontend

This exercise will demonstrate how to build a server and a web frontend solely in Dart. The nature of Dart allows it to be compiled for usage on the web (transpilation to JavaScript), on a server (via the Dart AOT runtime) and more.

3.1. Project setup

Create a new directory (or General project if you are using Eclipse) called gerrit_viewer.

In this directory, create two more folders server and frontend. These will host the respective code for the server and the web frontend.

If you are using Dartboard, instead create two projects called frontend and server by selecting File  New  Other and in there Dart  Dart Project. Create these new projects inside the gerrit_viewer project.

dartboard project location

3.2. Server

Create the files

  • bin/main.dart

  • lib/change.dart

  • lib/fetcher.dart

  • pubspec.yaml

inside the server directory.

Paste the following contents into pubspec.yaml.

name: server
description: A Dart server app

environment:
  sdk: '>=2.4.0 <3.0.0'

dependencies:
  json_annotation: ^2.0.0 (1)

dev_dependencies:
  # Your other dev_dependencies here
  build_runner: ^1.0.0
  json_serializable: ^2.0.0 (1)
1 As we are working with JSON data from the Gerrit server, it is useful to use serialization libraries instead of rolling our own logic here.

If you are using VSCode or Eclipse the Dart plugin should automatically sync the dependencies for you. If not, use the $ pub get command in the server directory to get the necessary dependencies.

3.2.1. Data Model

The changes from the Gerrit server are available as a list of JSON objects. These can be parsed into Dart objects. For this we use the @JsonSerializable() annotation from the json_serializable package.

Create the following class in the lib/change.dart file.

import 'package:json_annotation/json_annotation.dart';

part 'change.g.dart'; (1)

@JsonSerializable()
class Change {
  String id;
  String project;
  String branch;
  @JsonKey(name: 'change_id') (2)
  String changeId;
  String subject;
  @JsonKey(name: '_number')
  int number;

  Change(this.id, this.project, this.branch, this.changeId, this.subject,
      this.number);

  factory Change.fromJson(Map<String, dynamic> json) =>
      _$ChangeFromJson(json); (3)

  Map<String, dynamic> toJson() => _$ChangeToJson(this); (3)
}
1 You will see the error Target of URI hasn’t been generated: 'change.g.dart'. here. This is because there is a command that needs to be run before we can use the generated class. Run the command $ pub run build_runner build in the server directory to generate the class.
2 The @JsonKey(…​) annotation tells the generator that this field has a different name in the JSON data. In this case the key in the JSON is change_id. If the annotation is not provided the generator assumes the key is equal to the field’s name.
3 These special factory methods are also not available yet. They will be generated by the command above.

After the build command has completed there should be a new file in the server/lib directory called change.g.dart. You can open it but it should not be modified by hand as it is generated code.

The .g.dart suffix of Dart files typically means that they are generated by a tool or library. See 1 how to resolve errors.

3.2.2. Fetching the Data

For this demonstration we will use the Eclipse foundation hosted Gerrit instance. It allows to query all open changes of a project with a REST client.

The following URL contains all open changes in the platform/eclipse.platform.ui project: https://git.eclipse.org/r/changes/?q=status:open+project:platform/eclipse.platform.ui.

Fetching the data in Dart is pretty straightforward. The Dart core libraries contain a HttpClient that allows access to many operations of the HTTP protocol.

Create the following class in the lib/fetcher.dart file.

import 'dart:io';
import 'dart:convert';
import './change.dart';

const CHANGES_URL =
    'https://git.eclipse.org/r/changes/?q=status:open+project:platform/eclipse.platform.ui';

class Fetcher {
  Future<List<Change>> fetchChanges() async {
    HttpClientRequest request =
        await HttpClient().getUrl(Uri.parse(CHANGES_URL));
    var response = await request.close();
    var body = await response.transform(Utf8Decoder()).join();
    body = body.replaceAll(r")]}'",
        ""); (1)

    var changes = <Change>[];
    for (var x in jsonDecode(body)) { (2)
      changes.add(Change.fromJson(x));
    }
    return changes;
  }
}
1 Since the data from the Gerrit API has a special string that is not valid JSON in the first line of every response, it needs to be truncated in the client. See https://gerrit-review.googlesource.com/Documentation/rest-api.html#output (search for )]}') for more information on this.
2 jsonDecode(…​) is a method of the dart:convert package that allows to parse JSON into a Map<String, dynamic> that contains the data. Since the data from Gerrit is a list of objects we first need to parse each object of the list into a map that is then parsed into a new Change object.

The result of the fetchChanges() method is a List<Change>.

3.2.3. HTTP Server

Lastly, we still need a server that provides the parsed data to clients.

Traditionally the bin/main.dart is used as an entry point for any Dart program.

Use the following code to create the server in bin/main.dart.

import 'dart:convert';
import 'dart:io';

import '../lib/fetcher.dart';

Future main() async {
  var server = await HttpServer.bind(
    InternetAddress.loopbackIPv4, (1)
    4040, (2)
  );
  print('Listening on localhost:${server.port}');

  await for (HttpRequest request in server) {
    request.response.headers.add("Access-Control-Allow-Origin", "*"); (3)
    request.response.headers
        .add("Access-Control-Allow-Methods", "POST,GET,DELETE,PUT,OPTIONS");

    var changes = await new Fetcher().fetchChanges();
    request.response.write(jsonEncode(changes)); (4)
    await request.response.close();
  }
}
1 This is the address the server is bound to. In this case the local IPv4 "localhost" address.
2 The port the server is running on
3 CORS headers are needed as they allow requests from a host different to the current server. However this configuration is very insecure and should not be used in any production environment.
4 Here we encode the fetched data again to provide it as a JSON response to the clients. This block is executed on every request.

The server can be started using the command $ dart --observe bin/main.dart from the server directory. (The --observe flag is optional, it enables hot reloading code changes into the running process.)

The instance should now be available on http://localhost:4040.

3.3. Frontend

For the web front end we will use a plain Dart app that uses the dart:html package. It enables DOM manipulation and most of the common JavaScript operations.

3.3.1. Setup

The frontend directory should already be created. In it create the following files.

  • web/index.html

  • web/main.dart

  • web/style.css

  • pubspec.yaml

Use the following contents for pubspec.yaml.

name: web
description: An absolute bare-bones web app.

environment:
  sdk: '>=2.4.0 <3.0.0'

dependencies:
  server:
    path: ../server (1)

dev_dependencies:
  build_runner: ^1.5.0
  build_web_compilers: ^2.1.0
  pedantic: ^1.7.0
1 This path is the relative path of the root of the frontend directory to the server directory created earlier.

Eclipse or VSCode should run the dependency synchronization automatically. If not, synchronize the dependencies manually using $ pub get from the frontend directory.

For this project to be run on the web you also need the webdev CLI tool. You can install it globally with $ pub global activate webdev.

After the command has finished you can check its version with $ webdev --version.

3.3.2. HTML

Any Dart application that should be run on the web needs a static index.html file as its entry point.

Create the following inside web/index.html.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Gerrit viewer</title>
    <link rel="stylesheet" href="styles.css">
    <script defer src="main.dart.js"></script> (1)
</head>

<body>
  <div id="root"></div> (2)
</body>
</html>
1 The entry point of the compiled Dart file.
2 Any child elements will be added to this root element.

3.3.3. Styles

To make the application a little bit more pleasant to look at, add the following styles to the web/styles.css.

html, body {
  font-family: sans-serif;
}

#root {
  padding: 20px;
  max-width: 580px;
}

.change {
  background: #efefef;
  border: 1px solid #2c2c29;
  border-radius: 3px;
  display: block;
  margin-bottom: 5px;
  padding: 5px;
}

.change .subject {
  display: block;
  margin-bottom: 5px;
}

3.3.4. Dart

To run Dart code in a browser it must be transpiled to JavaScript (in short JS). The dart:html package provides bindings for most methods, functions and properties available in the JavaScript spec. If something is not available (either too complex or not implemented by choice) it is also possible to call JS code from Dart directly.

The web/main.dart file is the main file that we referenced in the index.html from above. Add the following content to web/main.dart.

import 'dart:convert';
import 'dart:html';
import 'dart:js';

import 'package:server/change.dart'; (1)

const CHANGES_URL = "http://localhost:4040"; (2)

void main() async {
  var root = querySelector('#root'); (3)
  root.children.addAll((await getChanges()).map(newChange));
}

Element newChange(Change change) {
  var element = DivElement()
    ..classes = ["change"]
    ..id = '${change.number}'; (4)

  element.children.add(AnchorElement()
    ..href = 'https://git.eclipse.org/r/#/c/${change.number}/'
    ..text = change.subject
    ..classes = ["subject"]);

  element.children.add(SpanElement()
    ..text = '${change.project} ${change.branch}'
    ..addEventListener('click', (event) => showDialog(change.branch)));

  return element;
}

void showDialog(String message) {
  context.callMethod('alert', [message]); (5)
}

Future<Iterable<Change>> getChanges() async {
  var changes = await HttpRequest.getString(CHANGES_URL);
  return (jsonDecode(changes) as List).map((json) => Change.fromJson(json));
}
1 The package of the server created earlier.
2 The URL of the server
3 The querySelector(…​) method is the equivalent of the document.querySelector(…​) method in JavaScript. It is provided by the dart:html package and returns any elements that match the CSS selector.
4 This "double-dot" syntax is the cascade syntax in Dart and allows to chain property setting operations and method calls on objects.
5 If a method or property is not available in Dart it can be accessed with the context object.

3.3.5. Running the App

Apps for the browser written in Dart need to be transpiled to JavaScript first. The webdev command builds (and rebuilds upon changes) the Dart files and serves them on a web server.

Run the command $ webdev serve --auto refresh in the frontend directory. (The --auto refresh parameter enables automatic refreshes in the browser whenever the code changes.)

After a few seconds the server should serve the app on localhost:8080. It should look like this.

Web frontend result

3.4. Recap

This exercise showed how to develop a web app and a data-proxy server solely in Dart. It is intended to show basic usage of the Dart language as well as showcase its use cases and capabilities on different platforms.

Find the full source code of this exercise at vogellacompany/codeexamples-dart/gerrit_viewer.

4. Programming in Dart

The following is a very short summary of important language constructs in Dart.

4.1. Comments

There are three types of comments:

// Single line comment

/*
Multi line
comment
*/

/// Documentation comments
/// These allow to reference types or variables via [square-brackets]
When you use documentation comments (///) generating Dartdoc can easily be done by using the dartdoc tool. This is distributed with the SDK.

4.2. Variables

Dart has type inference. This means there is a var keyword that tells the compiler, that it should determine the variable type from the return value. But variables can also be manually typed though it is favorable to use var in many cases.

var someVariable = "";
int someOtherVariable = 1;

Prefixing an identifier with an underscore enforces privacy in the Dart language.

4.2.1. static, final, const

static variables are available on the class itself instead of the instance.

final variables can not be reassigned. They must be initialized.

const means that the value of a variable can not be changed. This applies to List`s, classes and other types. Essentially the value of a `const variable is determined at compile time and can not be changed whatsoever at run time.

4.3. Parameters

A function can have two types of parameters: required and optional. T Some APIs — notably Flutter widget constructors — use only named parameters, even for parameters that are mandatory. When calling a function, you can specify named parameters using paramName: value. Although named parameters are a kind of optional parameter, you can annotate them with @required to indicate that the parameter is mandatory — that users must provide a value for the parameter. To use the @required annotation, depend on the meta package and import package:meta/meta.dart.

4.4. Classes

Since Dart is an object oriented language it has classes.

class ClassName {

}

Constructors are called upon instantiation of the class.

Populating fields in the constructor is very simple:

class ClassName {

    String someVar;

    ClassName(this.someVar); (1)
}
1 This automatically requires that one parameter of type String is passed to the constructor.

Optional parameter in constructors are also possible:

class ClassName {

    String someVar;

    ClassName({this.someVar}); (1)
}
1 Note the curly braces. This marks the parameter someVar as optional and the programmer can decide whether to populate the field or not.

4.4.1. Instantiation of a Class

A class can be instantiated using the new keyword:

ClassName("Some Parameter");

Optional parameters need to be named:

ClassName(someVar: "Some Value");

If the members of a class are final and are initialized in the constructor it can also be made const. This allows the class to be constructed at compile time.

class ClassName {

    final String someVar;

    const ClassName({this.someVar});
}

4.4.2. Inheritance

Dart classes have mixin-based inheritance. Every object is an instance of a class, and all classes inherit from Object.

5. Dart resources

6. vogella training and consulting support

Copyright © 2012-2019 vogella GmbH. Free use of the software examples is granted under the terms of the Eclipse Public License 2.0. This tutorial is published under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Germany license.