cv.cm

Coding · Programming languages

Dart tutorial for beginners

7 min read

Short answer

Dart is a statically typed, object-oriented language from Google, best known as the language of the Flutter app framework. It has sound null safety, compiles to native code and to JavaScript or WebAssembly, and supports fast hot reload during development. This page walks through setup, types, null safety, control flow, functions, collections, classes, async code and error handling.

What Dart is and what it is used for

Dart is a client-optimized programming language developed by Google. It is statically typed with type inference, has sound null safety, and compiles both to native machine code (for mobile, desktop and servers) and to JavaScript or WebAssembly (for the web). During development it runs on a virtual machine with hot reload, so changes appear in a running app within seconds.

Most people learn Dart to build apps with Flutter, Google's UI toolkit for Android, iOS, web, Windows, macOS and Linux from one codebase. Dart is also used for command-line tools and backend services.

Install Dart and run Hello, world

Install the Dart SDK from dart.dev (or install Flutter, which bundles Dart). Every Dart program starts at a top-level main function.

// hello.dart
void main() {
  print('Hello, world!');
}
dart run hello.dart
# Hello, world!
dart compile exe hello.dart   # builds a native executable
dart create my_app            # creates a new console project

You can also try every example on this page in DartPad, the browser-based editor at dartpad.dev.

Variables and basic types

Use var to let the compiler infer the type, final for a variable that is assigned once, and const for a compile-time constant. You can always write the type explicitly instead.

void main() {
  var name = 'Dart';        // inferred as String
  int year = 2011;          // explicit type
  final now = DateTime.now(); // set once, at run time
  const pi = 3.14159;       // compile-time constant
  double ratio = 2;         // int literal becomes 2.0
  bool ok = year > 2000;
  print('$name since $year, pi=${pi.toStringAsFixed(2)}, ok=$ok, ratio=$ratio');
  // Dart since 2011, pi=3.14, ok=true, ratio=2.0
  print(now.year > 2000);   // true
}
TypeExampleNotes
int42integer; 64-bit on native platforms
double3.1464-bit floating point
num7 or 7.5supertype of int and double
booltrueonly true and false are allowed in conditions
String'hi'UTF-16; supports $x and ${expr} interpolation
dynamicanythingturns off static checking; avoid in most code

On the web, int values are represented as JavaScript numbers, so very large integers can lose precision there.

Sound null safety

Since Dart 3, all Dart code uses sound null safety. A variable of type String can never be null; to allow null, write String?. The compiler checks every use, so null errors are caught before the program runs.

int lengthOf(String? s) {
  // return s.length;         // compile error: s might be null
  if (s == null) return 0;
  return s.length;            // promoted to String after the check
}
void main() {
  String? nickname;
  print(nickname?.length);    // null (null-aware access)
  print(nickname ?? 'guest'); // guest (default if null)
  nickname ??= 'dee';         // assign only if null
  print(lengthOf(nickname));  // 3
  late String config;         // initialized later, checked at run time
  config = 'loaded';
  print(config);              // loaded
}

Control flow

Dart has the usual if, for, while and do-while, plus a for-in loop over any iterable. Since Dart 3, switch supports pattern matching and can be used as an expression.

void main() {
  var scores = [72, 95, 48];
  for (var s in scores) {
    if (s >= 90) {
      print('$s excellent');
    } else if (s >= 50) {
      print('$s pass');
    } else {
      print('$s fail');
    }
  }
  for (var i = 0; i < 3; i++) {
    print('i = $i');
  }
  var day = 6;
  var kind = switch (day) {
    6 || 7 => 'weekend',
    >= 1 && <= 5 => 'weekday',
    _ => 'invalid',
  };
  print(kind); // weekend
}

Functions

Functions are first-class objects. Parameters can be positional, optional positional in [], or named in {}. Named parameters are optional unless marked required. The arrow syntax => is shorthand for a function that returns one expression.

int add(int a, int b) => a + b;
String greet(String name, {String greeting = 'Hello', required bool loud}) {
  var text = '$greeting, $name';
  return loud ? text.toUpperCase() : text;
}
String label(String text, [int? width]) =>
    width == null ? text : text.padRight(width, '.');
void main() {
  print(add(2, 3));                              // 5
  print(greet('Ana', loud: false));              // Hello, Ana
  print(greet('Ana', greeting: 'Hi', loud: true)); // HI, ANA
  print(label('id', 5));                         // id...
  var twice = (int x) => x * 2;                  // anonymous function
  print([1, 2, 3].map(twice).toList());          // [2, 4, 6]
}

Collections: List, Set, Map and records

The core collections are List (ordered), Set (unique items) and Map (key-value pairs). Collection literals support if, for and the spread operator .... Records, added in Dart 3, group several values without declaring a class.

void main() {
  var nums = [3, 1, 2];               // List<int>
  nums.add(4);
  nums.sort();
  print(nums);                        // [1, 2, 3, 4]
  var tags = {'dart', 'flutter', 'dart'};
  print(tags.length);                 // 2
  var ages = {'Ann': 31, 'Bo': 25};   // Map<String, int>
  ages['Cy'] = 40;
  print(ages['Bo']);                  // 25
  var showAll = false;
  var menu = ['Home', if (showAll) 'Admin', for (var n in nums) 'Item $n'];
  print(menu);  // [Home, Item 1, Item 2, Item 3, Item 4]
  (String, int) pair = ('Ann', 31);   // a record
  var (who, age) = pair;              // destructuring
  print('$who is $age');              // Ann is 31
}

Classes and async/await

Dart is object-oriented: every value is an object. Classes support constructors with initializing formals (this.x), named constructors, getters, mixins and interfaces. Asynchronous code uses Future and Stream with async and await, which keeps Flutter apps responsive while waiting for files or the network.

class Point {
  final double x, y;
  const Point(this.x, this.y);
  Point.origin() : x = 0, y = 0;     // named constructor
  double get sum => x + y;           // getter
  @override
  String toString() => 'Point($x, $y)';
}
Future<String> fetchUser() async {
  await Future.delayed(Duration(milliseconds: 100));
  return 'ana';
}
Future<void> main() async {
  print(Point(1, 2).sum);            // 3.0
  print(Point.origin());             // Point(0.0, 0.0)
  var user = await fetchUser();
  print('user: $user');              // user: ana
}

Error handling

Throw an exception with throw and handle it with try, on, catch and finally. Use on SomeType to catch a specific type. Dart distinguishes Exception (an expected problem the program can handle) from Error (a bug, such as a failed assertion or a range error) that you normally should not catch.

int parseAge(String s) {
  var n = int.tryParse(s);            // null instead of throwing
  if (n == null || n < 0) {
    throw FormatException('Not a valid age: $s');
  }
  return n;
}
void main() {
  for (var input in ['42', 'abc']) {
    try {
      print(parseAge(input));
    } on FormatException catch (e) {
      print('Error: ${e.message}');
    } finally {
      print('checked $input');
    }
  }
}
// 42 / checked 42 / Error: Not a valid age: abc / checked abc

A small complete program: shopping cart total

This program models products with a class, stores a cart in a map and prints a receipt. It combines classes, maps, string formatting and collection methods.

class Product {
  final String name;
  final double price;
  const Product(this.name, this.price);
}
void main() {
  const apple = Product('Apple', 0.5);
  const bread = Product('Bread', 2.25);
  final cart = <Product, int>{apple: 6, bread: 2};
  var total = 0.0;
  cart.forEach((product, qty) {
    final line = product.price * qty;
    total += line;
    print('${product.name.padRight(6)} x$qty  ${line.toStringAsFixed(2)}');
  });
  final count = cart.values.reduce((a, b) => a + b);
  print('$count items, total ${total.toStringAsFixed(2)}');
}
// Apple  x6  3.00
// Bread  x2  4.50
// 8 items, total 7.50

Where to go next

  • The Dart language tour and tutorials at dart.dev.
  • Effective Dart, the official style and design guide.
  • The Flutter documentation at flutter.dev if you want to build apps.
  • pub.dev, the package repository for Dart and Flutter.

FAQ

Is Dart only for Flutter?

No. Dart is a general-purpose language and can build command-line tools, servers and web apps. Flutter is simply the most popular reason to learn it.

Is Dart compiled or interpreted?

Both modes exist. During development Dart runs on a VM with just-in-time compilation and hot reload; for release it is compiled ahead of time to native machine code, or to JavaScript or WebAssembly for the web.

What is the difference between final and const in Dart?

A final variable is assigned once, possibly at run time. A const value must be known at compile time and is deeply immutable, which lets Flutter reuse constant widgets.

Is Dart similar to Java or JavaScript?

Its C-style syntax and classes feel familiar to Java, C# and JavaScript developers. Unlike JavaScript, Dart is statically typed and has sound null safety.

What does the ?? operator do in Dart?

a ?? b returns a if it is not null and otherwise b. The related a ??= b assigns b to a only when a is null.

All coding tutorials