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

2. 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.

2.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.

3. 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:

3.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.

3.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>

3.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.

3.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.

4. Exercise - Developing a Dart command line application

This exercise demonstrates how to build a command line Dart application.

In this exercise you access a REST API from the open source Gerrit server and print the result to the command line. Gerrit is a code review server originally developed for the Android system.

In this example we access the Gerrit server of the Eclipse foundation.

4.1. Project setup

Create a new folder (aka directory) called gerritreader.

Create the following files inside the new directory.

  • lib/main.dart

  • lib/change.dart

  • lib/fetcher.dart

  • pubspec.yaml

Paste the following contents into pubspec.yaml.

name: gerritreader
description: A Dart command line application

environment:
  sdk: '>=2.4.0 <3.0.0'

dependencies:
 http: ^0.12.0

This will make the Dark SDK and the http library available.

The Dart tooling (in VSCode, Eclipse or IntelliJ) will automatically synchronize the dependencies for you. If this does not work, use the $ pub get command in the main directory to update your dependencies.

4.2. Data Model

Create the following class in the lib/change.dart file which represents a Gerrit change request.

class Change {
  String id;
  String userId;
  String subject;
  String project;
  String status;
  DateTime updated;

  Change({this.userId, this.id, this.status, this.subject, this.project, this.updated});

  @override
  String toString() {
    return "Subject " + subject + " Updated " + updated.toString();
  }

  factory Change.fromJson(Map<String, dynamic> json) { (1)
   return Change(
      // userId: account['_account_id'],
      userId : json['owner']['_account_id'].toString(),
      id: json['change_id'],
      // more complex validation / conversion could be moved to a static method
      updated: DateTime.parse((json['updated']).substring(0, 10)),
      subject: json['subject'],
      status: json['status'],
      project: json['project'],
    );
  }
}
1 The factory method allows to create a Change object from a JSON file.

4.3. Fetching the server data

We use the following URL for testing platform/eclipse.platform.ui project: https://git.eclipse.org/r/changes/?q=status:open+project:platform/eclipse.platform.ui. The result contains all open Gerrits for the Eclipse platform UI project.

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

In this example we use the 'http' library which makes http calls even simpler.

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

import 'package:http/http.dart' as http;
import 'dart:convert';
import 'change.dart';

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

// set to false in case you don't want the JSON printed to the command line
const DEBUG = true;

class Fetcher { (1)
  Future<List<Change>> fetchChanges() async {
    http.Response reponse = await http.get(CHANGES_URL);
    var body = reponse.body.replaceAll(r")]}'", ""); (2)

    var changes = <Change>[];
    for (var json in jsonDecode(body)) {
      changes.add(Change.fromJson(json));
      if (DEBUG) {
        JsonEncoder encoder = new JsonEncoder.withIndent('  ');
        String prettyprint = encoder.convert(json);
        print(prettyprint);
      }
    }
    return changes;
  }
}
1 The result of the fetchChanges() method is a List<Change> wrapped in an asynchronous call.
2 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.

4.4. Using the API in the main method.

Now you will use the API you created.

import 'package:gerritreader/fetcher.dart';

main() async {
  Fetcher fetcher = Fetcher();
  await fetcher.fetchChanges();
}

If you see the message "Warning: Interpreting this as package URI" while running main.dart, try moving it to the bin folder, see https://github.com/dart-lang/sdk/issues/35279.

4.5. Test the program

Run your program via your IDE, e.g. in Visual Studio code via Debug  Start Debugging.

Also run it via the command line in the bin folder via dart main.dart.

You should see the JSON printed to the Debug Console in VS Code or to the command line.

4.6. Optional: Extend the query to read the user data

Extend the program so that you also read the user name via the REST API. You can use the following URI for that, replacing $userId with the userId of the Change object.

4.7. Optional: Output HTML

Put the following into a multi-line string in your main.dart file.

<!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">
</head>

<body>
  <div id="root">
  <table style="width:100%">
  PLACEHOLDER (1)
  </table>
  </div>
</body>
</html>
1 This will be replaced by the generated HTML.

Retrieve the list of Changes. Iterate over the list and and generate a HTML list from the list of changes.

For this use the String.replace method replacing PLACEHOLDER with the generated string. For search Change element create an entry similar to the following:

<tr>
<td> Summary fo one of the change items
</td>
<td> Time of last update
</td>
</tr>
</tl>

4.8. Optional: Write the HTML output to a file instead of the command line

Review https://api.dartlang.org/stable/2.6.1/dart-io/File-class.html to learn how to write Files in Dart and save the generated output as opengerrits.html.

5. Programming in Dart

The following is a very short summary of important language constructs in Dart. For more information see https://dart.dev/guides.

5.1. 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.

5.1.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});
}

5.2. Getter and setters

Getters and setters are special methods that provide read and write access to an object’s properties. You can provide additional getter and setter or override the implicit ones with the get and set keyword.

import 'dart:async';

class StringCreator {
  final StreamController _controller = StreamController();

  StringCreator() {
    Timer.periodic(Duration(seconds: 5), (t) => _push());
  }

  _push() {
    _controller.sink.add("Lars");
  }
  Stream<String> get stream => _controller.stream; (1)
}
1 defines a new accessor for the stream attribute, allows clients to access a new stream attribute

5.3. 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.

5.4. 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.

Variables can have the following modifies:

  • 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.

The value of a const variable is determined at compile time and can not be changed at run time.

5.5. 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.

5.5.1. Inheritance

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

5.6. Strings

Dart supports multi-line strings.

main() {
  var user = 'Test Joe';
  var message = """
      $user!
      Welcome to Programming Dart!
      """;
  print(message);
}

6. Dart resources

7. 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.