This tutorial gives a basic overview over the programming language Dart

1. Dart

The Dart programming language is a general purpose programming language developed by Google. It is widely used at Google and the programming language Flutter development.

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

Every Dart program specifies its dependencies in a pubspec.yaml file. This file is written in the YAML language.

For example the following is a pubspec.yaml file for a Flutter program (using Dart).

name: flutterui
description: A new Flutter project.

version: 1.0.0+1

environment:
  sdk: ">=2.7.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter
  cached_network_image: ^2.0.0
  fluttertoast: ^3.1.3

  cupertino_icons: ^0.1.2

dev_dependencies:
  flutter_test:
    sdk: flutter


# The following section is specific to Flutter.
flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

  assets:
   - images/a_dot_burr.jpeg
   - images/a_dot_ham.jpeg

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

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

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

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

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

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

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

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.

Feel free to skip to the next section to so some hands-on development with Dart.

5.1. Dart usage

The main method in Dart is named main() or main(List<String> args) if access to the command line arguments is needed. You can define code, i.e., variables, functions, getters and setters outside of classes. Dart does not have have keywords like public, private and protected. Dart uses 2-character indentation by convention.

5.2. Classes

Since Dart is an object oriented language it has classes. The following is an example of a class called Gerrit.

Gerrit is a code review system from Google. The object we use here as an example represents a single change (code review). We only use a very simple representation of this data structure for the purpose of demonstrating the Dart programming language.

class Gerrit {
  String id;
  String userId;
  String subject;
}

main () {
  // does nothing at the moment
}

Dart does not provide a separate interface keyword, as every class defines an interface.

class MySubTypeGerrit implements Gerrit {
  // more methods
}

Constructors are called upon instantiation of the class. It is valid in Dart that constructors have no body.

Populating fields in the constructor is very simple:

class Gerrit {

  String id;
  String userId;
  String subject;

  Gerrit(this.id, this.userId, this.subject); (1)
}
1 Constructor requires that several parameters of type String are passed to the constructor. Using this in a constructor’s parameter list assigns values to instance variables.

The following code is equivalent:

class Gerrit {

  String id;
  String userId;
  String subject;

  Gerrit(id, userId, subject) {
    this.id = id;
    this.userId = userId;
    this.subject = subject;
  }
}

You can define that parameters are optional parameters by putting them into {} braces.

class Gerrit {

  String id;
  String userId;
  String subject;

  Gerrit({this.id, this.userId, this.subject="default"});(1)
}
1 Note the curly braces. This marks the parameters as optional and the programmer can decide whether to populate the field or not. It is also possible to assign default values if the relevant parameter is not used.

You can also initialize instance variables before the constructor body runs via a initializer list. Separate initializers with commas.

class Gerrit {
  String id;
  String userId;
  String subject;

  Gerrit.fromJson(Map<String, String> json)
      : id = json['id'],
        userId = json['userId'],
        subject = json['subject'];
}

You can define a tostring method to define the String representation of your class.

class Gerrit {

  // as before

  @override                                 (1)
  String toString() => 'Gerrit: $subject';  (2)
}
1 Tell the Dart compiler that you want to override a member
2 Shorten one-line functions or methods using fat arrow (⇒) notation. Dart supports single or double quotes when specifying strings. Use string interpolation to put the value of an expression inside a string literal: ${expression}. If the expression is an identifier, you can skip the braces: $variableName.

5.2.1. Instantiation of a Class

A class can be instantiated with or without the optional new keyword

main() {
  var gerrit = Gerrit("1", "vogella" "Testing"); // using new is optional
  print(gerrit);
  final g2 = Gerrit("2", "vogella" "Testing"); (1)
}
1 if the variable value does not change, you can use final instead of var.

if optional parameters are used, you need to use named parameters to tell Dart which parameter is used.

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.3. Getter and setters

By default, Dart provides implicit getters and setters for all public instance variables You can provide additional getter and setter or override the implicit ones with the get and set keyword. Access to instance variable is the same, no matter if you define a settter or getter or not.

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.4. Using factory constructors

Using the factory keyword you can define a factory method which returns certain instances. If you name your factory with the default constructor name you have to have a named constructor. Using _ makes the named constructor internal.

class Gerrit {

  String id;
  String userId;
  String subject;
  factory Gerrit (int i) {
    // create instance of Gerrit based on the int information and return it
    return Gerrit._internal(i.toString(), "MyUser", "Topic 1");
  }

  Gerrit._internal({this.id, this.userId, this.subject="default"});(1)
}

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

A function can have two types of parameters: required and optional. 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.7.1. Inheritance

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

5.8. Strings

Dart supports multi-line strings.

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

5.9. Lists and Maps

Dart provides native support for list and maps and allows to construct it directly

For example, the following is a definition of list in which each list element is a map.

final dummySnapshot = [
 {"name": "Filip", "votes": 15},
 {"name": "Justin", "votes": 1},
];

5.10. Method cascades

Dart supports writing fluent API via method cascades. You can call any method after each other via the .. operator.

class Test {
  void doIt(){}
  String returnIt() {return "s";}

}
main() {
  var t = Test();
  t..returnIt()..doIt();
  t..doIt()..returnIt();
}

5.11. Null aware operations

Dart supports null aware operations:

  • expression1 ?? expression2 - evaluates to expression1 if this is not null, otherwise it evaluates to expression2

  • x ??= expression - assigns x to expression only if x is null

  • x?.p - evaluates to x.p if x is not null, otherwise to null

main() {
  var x = null;
  print (x ?? "Default text");

  x ??= "Hello";

  print(x?.toString());
}

5.12. Casting in Dart

The instanceof-operator is called is in Dart.

class Foo { }

main() {
  var foo = new Foo();
  if (foo is Foo) {
    print("It is a foo");
  }
}

You can also explicit cast via the as operator.

(emp as Person).firstName = 'Bob';

5.13. Extension methods

Via extension methods you can add functionality to existing libraries.

For this, you need to require at least Dart 2.7.0 in your pubspec.yaml.

environment:
  sdk: ">=2.7.0 <3.0.0"

The syntax for implementing an extension is the following:

extension <extension name> on <type> {
  (<member definition>)*
}

For example, you can add a method to the String data type.

extension NumberParsing on String {
  int parseInt() {
    return int.parse(this);
  }
}

  main() {
   int i = "12".parseInt();
  }

You can import extensions from a file and use show and hide to allow or permit certain extensions.

// Defines the String extension method parseInt().
import 'string_apis.dart';

// Also defines parseInt(), but hiding NumberParsing2
// hides that extension method.
import 'string_apis_2.dart' hide NumberParsing2;

If both extensions have the same name, then you might need to import using a prefix:

import 'string_apis_3.dart' as rad;

// Use the ParseNumbers extension from string_apis_3.dart.
print(rad.NumberParsing('42').parseInt());

6. Handling http requests in Dart

The package http/http.dart package provides an easy way of performing http requests. To use it you have to add it to your pubspec.yaml file.

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

http.Response response =
    await http.get("https://jsonplaceholder.typicode.com/todos");
if (response.statusCode == 200) {
  String body = response.body;
  print(body);
}

7. Handling JSON data in Flutter

The built-in dart package dart:convert provides a few methods for working with JSON data. The two main methods are:

  • jsonEncode(Object object): Serializes a Dart Object into a JSON string

  • jsonDecode(String source): Deserializes a JSON string into a Dart object

jsonEncode and jsonDecode are shorthands for json.Encode and json.Decode and are useful in case a local variable is called json.

To deserialize a String of JSON, use jsonDecode:

var json = '{"key": "Some Data"}';
var data = jsonDecode(json);
print(data['key']); // "Some Data"

Similarly to serialize a Dart object into a JSON String, use jsonEncode:

var data = {'key': 'Some Data'};
var json = jsonEncode(data);
print(json); // {"key": "Some Data"}

By default only the following data types are supported:

  • numbers (int, float, double, etc.)

  • boolean

  • String

  • null

  • a list

  • Map (with String keys)

However, if the value is not of any of the above types, the encoder will try calling toJson on it. You can use this to implement custom serialization logic for you objects:

class DataClass {
    int id;
    String name;

    DataClass(this.id, this.name);

    Map<String, dynamic> toJson() => { 'id': id, 'name': name };
}

var json = jsonEncode(DataClass(4, 'Some name'));
print(json); //  {"id": 4, "name": "Some name"}

If you don’t have control over the object, you can alternatively use the optional second parameter in jsonEncode, toEncodable. It takes a function that receives the object passed in and returns some custom serialization logic.

var json = jsonEncode(DataClass(6, 'Some other Name'), (DataClass value) => { 'id': id, 'name': name });
print(json); // {"id": 4, "name": "Some name"}

Here is a small example, which downloads a JSON from the Internet and converts it into a data object.

Future<List<Todo>> _getTodos() async {
  List<Todo> todos = List();
  http.Response response =
      await http.get("https://jsonplaceholder.typicode.com/todos");
  if (response.statusCode == 200) {
    String body = response.body;
    var json = jsonDecode(body);
    for (Map<String, dynamic> entry in json) {
      var userId = entry['userId'];
      todos.add(Todo(userId: userId));
    }
  }
  return todos;
}

8. Programming in Dart

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

Feel free to skip to the next section to so some hands-on development with Dart.

8.1. Dart usage

The main method in Dart is named main() or main(List<String> args) if access to the command line arguments is needed. You can define code, i.e., variables, functions, getters and setters outside of classes. Dart does not have have keywords like public, private and protected. Dart uses 2-character indentation by convention.

8.2. Classes

Since Dart is an object oriented language it has classes. The following is an example of a class called Gerrit.

Gerrit is a code review system from Google. The object we use here as an example represents a single change (code review). We only use a very simple representation of this data structure for the purpose of demonstrating the Dart programming language.

class Gerrit {
  String id;
  String userId;
  String subject;
}

main () {
  // does nothing at the moment
}

Dart does not provide a separate interface keyword, as every class defines an interface.

class MySubTypeGerrit implements Gerrit {
  // more methods
}

Constructors are called upon instantiation of the class. It is valid in Dart that constructors have no body.

Populating fields in the constructor is very simple:

class Gerrit {

  String id;
  String userId;
  String subject;

  Gerrit(this.id, this.userId, this.subject); (1)
}
1 Constructor requires that several parameters of type String are passed to the constructor. Using this in a constructor’s parameter list assigns values to instance variables.

The following code is equivalent:

class Gerrit {

  String id;
  String userId;
  String subject;

  Gerrit(id, userId, subject) {
    this.id = id;
    this.userId = userId;
    this.subject = subject;
  }
}

You can define that parameters are optional parameters by putting them into {} braces.

class Gerrit {

  String id;
  String userId;
  String subject;

  Gerrit({this.id, this.userId, this.subject="default"});(1)
}
1 Note the curly braces. This marks the parameters as optional and the programmer can decide whether to populate the field or not. It is also possible to assign default values if the relevant parameter is not used.

You can also initialize instance variables before the constructor body runs via a initializer list. Separate initializers with commas.

class Gerrit {
  String id;
  String userId;
  String subject;

  Gerrit.fromJson(Map<String, String> json)
      : id = json['id'],
        userId = json['userId'],
        subject = json['subject'];
}

You can define a tostring method to define the String representation of your class.

class Gerrit {

  // as before

  @override                                 (1)
  String toString() => 'Gerrit: $subject';  (2)
}
1 Tell the Dart compiler that you want to override a member
2 Shorten one-line functions or methods using fat arrow (⇒) notation. Dart supports single or double quotes when specifying strings. Use string interpolation to put the value of an expression inside a string literal: ${expression}. If the expression is an identifier, you can skip the braces: $variableName.

8.2.1. Instantiation of a Class

A class can be instantiated with or without the optional new keyword

main() {
  var gerrit = Gerrit("1", "vogella" "Testing"); // using new is optional
  print(gerrit);
  final g2 = Gerrit("2", "vogella" "Testing"); (1)
}
1 if the variable value does not change, you can use final instead of var.

if optional parameters are used, you need to use named parameters to tell Dart which parameter is used.

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

8.3. Getter and setters

By default, Dart provides implicit getters and setters for all public instance variables You can provide additional getter and setter or override the implicit ones with the get and set keyword. Access to instance variable is the same, no matter if you define a settter or getter or not.

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

8.4. Using factory constructors

Using the factory keyword you can define a factory method which returns certain instances. If you name your factory with the default constructor name you have to have a named constructor. Using _ makes the named constructor internal.

class Gerrit {

  String id;
  String userId;
  String subject;
  factory Gerrit (int i) {
    // create instance of Gerrit based on the int information and return it
    return Gerrit._internal(i.toString(), "MyUser", "Topic 1");
  }

  Gerrit._internal({this.id, this.userId, this.subject="default"});(1)
}

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

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

8.7. Parameters

A function can have two types of parameters: required and optional. 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.

8.7.1. Inheritance

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

8.8. Strings

Dart supports multi-line strings.

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

8.9. Lists and Maps

Dart provides native support for list and maps and allows to construct it directly

For example, the following is a definition of list in which each list element is a map.

final dummySnapshot = [
 {"name": "Filip", "votes": 15},
 {"name": "Justin", "votes": 1},
];

8.10. Method cascades

Dart supports writing fluent API via method cascades. You can call any method after each other via the .. operator.

class Test {
  void doIt(){}
  String returnIt() {return "s";}

}
main() {
  var t = Test();
  t..returnIt()..doIt();
  t..doIt()..returnIt();
}

8.11. Null aware operations

Dart supports null aware operations:

  • expression1 ?? expression2 - evaluates to expression1 if this is not null, otherwise it evaluates to expression2

  • x ??= expression - assigns x to expression only if x is null

  • x?.p - evaluates to x.p if x is not null, otherwise to null

main() {
  var x = null;
  print (x ?? "Default text");

  x ??= "Hello";

  print(x?.toString());
}

8.12. Casting in Dart

The instanceof-operator is called is in Dart.

class Foo { }

main() {
  var foo = new Foo();
  if (foo is Foo) {
    print("It is a foo");
  }
}

You can also explicit cast via the as operator.

(emp as Person).firstName = 'Bob';

8.13. Extension methods

Via extension methods you can add functionality to existing libraries.

For this, you need to require at least Dart 2.7.0 in your pubspec.yaml.

environment:
  sdk: ">=2.7.0 <3.0.0"

The syntax for implementing an extension is the following:

extension <extension name> on <type> {
  (<member definition>)*
}

For example, you can add a method to the String data type.

extension NumberParsing on String {
  int parseInt() {
    return int.parse(this);
  }
}

  main() {
   int i = "12".parseInt();
  }

You can import extensions from a file and use show and hide to allow or permit certain extensions.

// Defines the String extension method parseInt().
import 'string_apis.dart';

// Also defines parseInt(), but hiding NumberParsing2
// hides that extension method.
import 'string_apis_2.dart' hide NumberParsing2;

If both extensions have the same name, then you might need to import using a prefix:

import 'string_apis_3.dart' as rad;

// Use the ParseNumbers extension from string_apis_3.dart.
print(rad.NumberParsing('42').parseInt());

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

9.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 makes the Dark SDK and the http library available.

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

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

9.3. Fetching the Data

We use the following URL for testing platform/eclipse.platform.ui project:

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.

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

9.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 lib folder via dart main.dart.

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

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

9.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 each Change element create an HTML list entry similar to the following:

<tr>
<td> Summary of the Change ID
</td>
<td> Last time updated
</td>
</tr>
</tl>

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

10. Dart resources

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