Grundlagen
Hello World
main() ist der Einstiegspunkt des Dart-Programms
// Hello World in Dart
void main() {
print('Hello, World!');
}
/* Multi-line
block comment */
/// Documentation comment
/// Supports Markdown
void greet(String name) {
print('Hi, $name');
}Kommentare
Dokumentationskommentare unterstützen Markdown
var name = 'Alice'; // type inferred
String city = 'NYC'; // explicit type
final age = 30; // runtime constant
const PI = 3.14; // compile-time constant
final now = DateTime.now(); // OK: runtime value
// const time = DateTime.now(); // ERROR: not compile-time
const list = [1, 2, 3]; // const list (immutable)
final list2 = [4, 5, 6]; // final ref, mutable contentSemikolons
Jede Anweisung muss mit einem Semikolon enden
int value = 42; // non-nullable
int? nullableValue; // nullable (default null)
print(nullableValue); // null
print(nullableValue ?? 0); // 0 (null coalescing)
nullableValue ??= 10; // assign if null
print(nullableValue); // 10
// int x = null; // ERROR: non-nullable
String name = 'Alice';
print(name.length); // safe, no null checkAusgabe
print fügt automatisch einen Zeilenumbruch hinzu
import 'dart:io';
void main() {
print('Hello'); // stdout with newline
stdout.write('no newline'); // no trailing newline
stderr.writeln('an error'); // stderr with newline
String? input = stdin.readLineSync(); // read a line
int? n = int.tryParse(input ?? ''); // safe parse
print('You entered: $n');
}Variablendeklarations-Schlüsselwörter
var/final bevorzugen
var x = 10; // inferred as int
var y = 3.14; // inferred as double
var s = 'hi'; // inferred as String
dynamic d = 10; // type can change
d = 'now string'; // OK
// d.foo(); // compiles, may fail at runtime
Object o = 'hello'; // supertype of all non-null types
// o.length; // ERROR: Object has no length
print((o as String).length); // 5 (cast)Variablen
var-Typinferenz
Der Compiler leitet den Typ automatisch ab
int a = 42;
double b = 3.14;
num c = 10; // num is supertype of int & double
num d = 2.71;
print(a.bitLength); // 6
print(b.toStringAsFixed(2)); // '3.14'
print(10 ~/ 3); // 3 (integer division)
print(10.remainder(3)); // 1
print(0xFF); // 255 (hex)
print(1.5e3); // 1500.0 (scientific)final und const
const ist strenger als final
var s1 = 'single';
var s2 = "double";
var s3 = '''multi
line string''';
var name = 'Alice';
print('Hi, $name'); // interpolation
print('Length: ${name.length}'); // expression interpolation
var raw = r'No escape: \n'; // raw string (literal)
var escaped = 'It\'s ok'; // escaped quote
print(s1[0]); // 's' (index access)late-Variablen
Verzögerte Initialisierung, muss vor der ersten Verwendung zugewiesen werden
bool isTrue = true;
bool isFalse = false;
print(!isTrue); // false
print(isTrue && isFalse); // false
print(isTrue || isFalse); // true
// Only bool is allowed in conditions; no truthy/falsy
// if (1) {} // ERROR: must be bool
if ('text'.isNotEmpty) {
print('non-empty');
}dynamic-Typ
Deaktiviert die Typprüfung, mit Vorsicht verwenden
var list = [1, 2, 3];
var typed = <String>['a', 'b'];
var constList = const [1, 2, 3];
list.add(4);
list.addAll([5, 6]);
print(list.length); // 6
print(list[0]); // 1
print(list.sublist(1, 3)); // [2, 3]
var spread = [...list, 7]; // spread
// constList.add(0); // ERROR: immutableKonstante Konstruktoren
const-Konstruktor verwenden, um Compile-Zeit-Konstanten zu erstellen
var map = {
'name': 'Alice',
'age': 30,
};
var typed = <String, int>{'a': 1, 'b': 2};
map['city'] = 'NYC'; // add entry
print(map['name']); // Alice
print(map.length); // 3
print(map.containsKey('age')); // true
map.forEach((k, v) => print('$k: $v'));
var keys = map.keys.toList(); // [name, age, city]Sets & Runes
Sets are unordered collections of unique elements—useful for deduplication and set operations (union, intersection, difference). Runes expose the Unicode code points of a string, which is needed for emoji and non-BMP characters stored as surrogate pairs in UTF-16.
var set = {1, 2, 3};
set.add(2); // no duplicate added
set.add(4);
print(set); // {1, 2, 3, 4}
print(set.contains(2)); // true
print(set.intersection({2, 3, 5})); // {2, 3}
// Runes (Unicode code points)
var heart = '♥'; // ♥
print(heart); // ♥
print('A'.codeUnitAt(0)); // 65Datentypen
Zahlentypen
num ist der Obertyp von int und double
print(5 + 3); // 8
print(5 - 3); // 2
print(5 * 3); // 15
print(5 / 3); // 1.6666... (double)
print(5 ~/ 3); // 1 (integer division)
print(5 % 3); // 2 (modulo)
print(-(5)); // -5 (unary minus)
print(2.toString()); // '2'Strings
Unterstützt einfache, doppelte und dreifache Anführungszeichen
var i = 5;
print(i++); // 5 (postfix: use, then add)
print(i); // 6
print(++i); // 7 (prefix: add, then use)
print(i--); // 7
print(--i); // 5Boolescher Typ
Nur true- und false-Werte
print(3 == 3); // true
print(3 != 4); // true
print(3 < 4); // true
print(3 > 4); // false
print(3 <= 3); // true
print(3 >= 4); // false
print('a' == 'a'); // true (content equality)
var l1 = [1, 2];
var l2 = [1, 2];
print(l1 == l2); // false (reference equality)List
Ähnlich wie Arrays in anderen Sprachen
bool a = true, b = false;
print(a && b); // false
print(a || b); // true
print(!a); // false
// short-circuit evaluation
bool check() { print('called'); return true; }
false && check(); // check() NOT called
true || check(); // check() NOT calledMap
Schlüssel-Wert-Paar-Sammlung
Object x = 'hello';
print(x is String); // true
print(x is! int); // true
if (x is String) {
print(x.length); // smart-cast to String
}
Object y = 42;
print((y as int) + 1); // 43 (cast)
// (y as String); // runtime TypeErrorSet
Ungeordnete Sammlung eindeutiger Elemente
class Builder {
String? name;
int? size;
Builder setName(String n) { name = n; return this; }
Builder setSize(int s) { size = s; return this; }
}
var b = Builder()
..setName('widget')
..setSize(10);
print(b.name); // widget
String? path;
print(path?.length); // null (safe access)
print(path?.length ?? 0); // 0Operatoren
Arithmetische Operatoren
~/ ist Darts Ganzzahl-Divisionsoperator
int score = 85;
if (score >= 90) {
print('A');
} else if (score >= 80) {
print('B');
} else {
print('C');
}
// ternary expression
var grade = score >= 60 ? 'pass' : 'fail';
print(grade); // pass
// if (score) {} // ERROR: condition must be boolInkrement und Dekrement
Präfix: Operation dann Zuweisung, Postfix: Zuweisung dann Operation
for (var i = 0; i < 3; i++) {
print(i);
}
var list = ['a', 'b', 'c'];
for (var item in list) {
print(item);
}
// for-in with Map entries
var map = {'x': 1, 'y': 2};
for (var entry in map.entries) {
print('${entry.key}: ${entry.value}');
}Typ-Test-Operatoren
as führt Typumwandlung durch
var i = 0;
while (i < 3) {
print('while $i');
i++;
}
var j = 0;
do {
print('do $j');
j++;
} while (j < 3);Bedingte Ausdrücke
?? ist der Null-Coalescing-Operator
// Classic switch statement
var color = 'red';
switch (color) {
case 'red':
print('stop');
break;
case 'green':
print('go');
break;
default:
print('unknown');
}
// Dart 3 switch expression
String describe(int n) => switch (n) {
0 => 'zero',
1 || 2 => 'small',
>= 3 && <= 10 => 'medium',
_ => 'large',
};
print(describe(5)); // mediumCascade-Operator
.. ermöglicht verkettete Aufrufe, die das Objekt selbst zurückgeben
for (var i = 0; i < 5; i++) {
if (i == 2) continue; // skip 2
if (i == 4) break; // stop at 4
print(i); // 0, 1, 3
}
// labels for nested loops
outer:
for (var i = 0; i < 3; i++) {
for (var j = 0; j < 3; j++) {
if (i + j > 2) break outer;
print('$i,$j');
}
}assert
assert(condition, message) checks a condition during development. Assertions are enabled in debug mode and removed in production (release) builds. Use them for internal invariants and debugging—not for input validation that must run in production.
void setAge(int age) {
assert(age >= 0, 'age must be non-negative');
// ...
}
assert(1 == 1); // passes in debug
// assert(1 == 2, 'math is broken'); // fails in debug
// Assertions are stripped in release/production buildsKontrollfluss
if-else
Bedingung muss vom Typ bool sein
// named function with return type
int add(int a, int b) {
return a + b;
}
// functions are first-class objects
int Function(int) makeAdder(int n) {
return (int x) => x + n;
}
var add5 = makeAdder(5);
print(add5(3)); // 8
print(add(2, 3)); // 5for-Schleife
Unterstützt C-Stil und for-in
// single-expression function with =>
int square(int x) => x * x;
String greet(String name) => 'Hi, $name';
// arrow with nullable
String? firstChar(String? s) => s?.isEmpty ?? true ? null : s[0];
print(square(4)); // 16
print(greet('Al')); // Hi, Al
print(firstChar('hi')); // hwhile-Schleife
do-while wird mindestens einmal ausgeführt
// optional positional params wrapped in []
String greet(String name, [String? title]) {
if (title != null) {
return 'Hello, $title $name';
}
return 'Hello, $name';
}
print(greet('Alice')); // Hello, Alice
print(greet('Bob', 'Dr.')); // Hello, Dr. Bobswitch
Dart 3 unterstützt switch-Ausdrücke
// named params wrapped in {}, required marks mandatory
void createUser({
required String name,
int age = 0,
String? email,
}) {
print('$name, $age, $email');
}
createUser(name: 'Alice', age: 30);
createUser(name: 'Bob', email: '[email protected]');
// named params are passed by name, order-independentbreak und continue
break verlässt die Schleife, continue überspringt diese Iteration
// default values for optional positional
double calc(double a, [double b = 1.0, double c = 0.0]) {
return a * b + c;
}
// default values for named
void config({String host = 'localhost', int port = 8080}) {
print('$host:$port');
}
print(calc(5)); // 5.0
print(calc(5, 2)); // 10.0
config(port: 3000); // localhost:3000Anonymous Functions & Closures
Anonymous functions (lambdas) have no name and are often assigned to variables or passed as callbacks. Closures capture variables from their enclosing scope and keep them alive. The parameter list can be typed (int a, int b) or untyped (a, b).
// anonymous function assigned to a variable
var multiply = (int a, int b) => a * b;
print(multiply(3, 4)); // 12
// closure capturing a variable
Function counter() {
int count = 0;
return () => ++count;
}
var c = counter();
print(c()); // 1
print(c()); // 2
// used as callbacks
[1, 2, 3].forEach((n) => print(n));Funktionen
Funktionsdeklaration
In Dart sind Funktionen Objekte erster Klasse
class Person {
String name;
int age;
// constructor
Person(this.name, this.age);
void greet() {
print('Hi, I am $name');
}
}
var p = Person('Alice', 30);
p.greet(); // Hi, I am Alice
print(p.name); // AlicePfeilfunktionen
=> wird für Ein-Ausdrucks-Funktionen verwendet
class Point {
double x;
double y;
Point(this.x, this.y);
double distanceTo(Point other) {
return ((x - other.x) * (x - other.x) +
(y - other.y) * (y - other.y));
}
void moveBy(double dx, double dy) {
this.x += dx; // 'this' is optional when unambiguous
y += dy;
}
}Optionale Parameter
[] umschließt positionelle optionale Parameter
class Rectangle {
double width, height;
Rectangle(this.width, this.height);
// computed getter
double get area => width * height;
set size(double v) {
width = v;
height = v;
}
}
var r = Rectangle(3, 4);
print(r.area); // 12 (accessed like a field)
r.size = 10;
print(r.area); // 100Benannte Parameter
{} umschließt benannte Parameter, required gibt Pflichtparameter an
class MathUtils {
static const double PI = 3.14159;
static double circleArea(double r) => PI * r * r;
}
// accessed via the class, not an instance
print(MathUtils.PI); // 3.14159
print(MathUtils.circleArea(2)); // 12.566
// static members belong to the class, not instancesStandardwerte
Optionale Parameter können Standardwerte haben
class Logger {
final String name;
static final Map<String, Logger> _cache = {};
// factory may return a cached instance
factory Logger(String name) {
return _cache.putIfAbsent(name, () => Logger._internal(name));
}
Logger._internal(this.name);
}
var a = Logger('app');
var b = Logger('app');
print(identical(a, b)); // true (same cached instance)Anonyme Funktionen
Anonyme Funktionen werden oft als Callbacks verwendet
class Book {
String title;
Book(this.title);
@override
String toString() => 'Book($title)';
@override
bool operator ==(Object other) =>
other is Book && other.title == title;
@override
int get hashCode => title.hashCode;
}
print(Book('Dart')); // Book(Dart)
print(Book('A') == Book('A')); // trueKlassen
Klassendefinition
Der Konstruktor hat denselben Namen wie die Klasse
class Animal {
String species;
// generative constructor
Animal(this.species);
// if no constructor is written, Dart provides:
// Animal() : species = 'unknown';
}
var a = Animal('cat');
print(a.species); // catBenannte Konstruktoren
Eine Klasse kann mehrere benannte Konstruktoren haben
class Point {
double x, y;
Point(this.x, this.y);
// named constructor
Point.origin() : x = 0, y = 0;
Point.fromList(List<double> l) : x = l[0], y = l[1];
}
var p1 = Point.origin();
var p2 = Point.fromList([3, 4]);
print('${p1.x},${p1.y}'); // 0.0,0.0
print('${p2.x},${p2.y}'); // 3.0,4.0Getter und Setter
get/set-Schlüsselwörter verwenden
class Temperature {
final double celsius;
// initializer list runs before body
Temperature(double c) : celsius = c;
Temperature.fromFahrenheit(double f)
: celsius = (f - 32) * 5 / 9;
// assert in initializer list
Temperature.clamped(double c)
: assert(c >= -273.15),
celsius = c < -273.15 ? -273.15 : c;
}
print(Temperature.fromFahrenheit(32).celsius); // 0.0Statische Member
statische Member gehören zur Klasse, nicht zu Instanzen
class Point {
double x, y;
Point(this.x, this.y);
// redirect to another constructor with 'this'
Point.alongX(double x) : this(x, 0);
Point.origin() : this(0, 0);
Point.fromDouble(double n) : this.alongX(n);
}
print(Point.alongX(5).y); // 0.0
print(Point.origin().x); // 0.0Factory-Konstruktor
factory erstellt nicht immer eine neue Instanz
class ImmutablePoint {
final double x;
final double y;
// const constructor: creates compile-time constant instances
const ImmutablePoint(this.x, this.y);
static const origin = ImmutablePoint(0, 0);
}
const p = ImmutablePoint(1, 2);
const o = ImmutablePoint.origin;
print(identical(o, ImmutablePoint(0, 0))); // trueFactory & Caching
Factory constructors can return const or cached instances and may use Dart 3 switch expressions. They differ from generative constructors which always create a new instance. Use factories when construction logic must choose what to return.
class Shape {
final String type;
const Shape._(this.type);
factory Shape(String kind) {
return switch (kind) {
'circle' => const Shape._('circle'),
'square' => const Shape._('square'),
_ => const Shape._('unknown'),
};
}
}
print(Shape('circle').type); // circle
print(identical(Shape('circle'), Shape('circle'))); // trueVererbung
extends-Vererbung
Dart hat einfache Vererbung
class Animal {
String name;
Animal(this.name);
void speak() => print('$name makes a sound');
}
class Dog extends Animal {
Dog(String name) : super(name);
@override
void speak() => print('$name barks');
}
var d = Dog('Rex');
d.speak(); // Rex barkssuper-Aufruf
@override markiert Methodenüberschreibung
class Vehicle {
int speed = 0;
void accelerate(int by) => speed += by;
void describe() => print('Vehicle at $speed');
}
class Car extends Vehicle {
@override
void accelerate(int by) {
super.accelerate(by); // call parent method
print('Car now at $speed');
}
}
Car().accelerate(10); // Car now at 10Abstrakte Klassen
Abstrakte Klassen können nicht instanziiert werden
class Base {
String tag;
Base(this.tag) {
print('Base created: $tag');
}
}
class Derived extends Base {
Derived(String tag) : super(tag) {
print('Derived created');
}
}
// order: super initializer -> super body -> derived body
Derived('x');
// Base created: x
// Derived createdSchnittstellenimplementierung
Jede Klasse definiert implizit eine Schnittstelle
class Proxy implements Object {
@override
dynamic noSuchMethod(Invocation inv) {
print('Called: ${inv.memberName}');
return null;
}
}
var p = Proxy();
p.someMissingMethod(); // Called: Symbol("someMissingMethod")noSuchMethod
Behandelt Aufrufe nicht vorhandener Methoden
sealed class Shape {
const Shape();
}
class Circle extends Shape {
final double r;
const Circle(this.r);
}
class Square extends Shape {
final double s;
const Square(this.s);
}
// switch is exhaustive over sealed subtypes
double area(Shape s) => switch (s) {
Circle(:var r) => 3.14 * r * r,
Square(:var s) => s * s,
};
print(area(const Circle(2))); // 12.56Mixins
Mixin definieren
mixin kann keinen Konstruktor haben
abstract class Animal {
// abstract method: no body, must be overridden
void makeSound();
// concrete method: inherited as-is
void breathe() => print('breathing');
}
class Cat extends Animal {
@override
void makeSound() => print('meow');
}
var c = Cat();
c.makeSound(); // meow
// Animal(); // ERROR: cannot instantiate abstract classMixin verwenden
with-Schlüsselwort verwenden, um mehrere Mixins zu nutzen
class Television {
void turnOn() => print('on');
void turnOff() => print('off');
}
// every class implicitly defines an interface
class SmartTV implements Television {
@override
void turnOn() => print('smart on');
@override
void turnOff() => print('smart off');
}
SmartTV().turnOn(); // smart onmixin-Einschränkungen
on beschränkt mixin auf bestimmte Klassen
abstract class Flyer {
void fly();
}
abstract class Swimmer {
void swim();
}
// implement multiple interfaces
class Duck implements Flyer, Swimmer {
@override
void fly() => print('flying');
@override
void swim() => print('swimming');
}
Duck().fly(); // flying
Duck().swim(); // swimmingmixin class
Dart 3 unterstützt mixin class
class Base {
void greet() => print('hello');
void wave() => print('waving');
}
// extends: reuse implementation, single parent
class A extends Base {
@override
void greet() => print('A says hi');
}
// implements: contract only, must override all
class B implements Base {
@override
void greet() => print('B says hi');
@override
void wave() => print('B waving');
}Abstract vs Interface Guidelines
Use abstract classes to share implementation among closely related types (inheritance of code). Use interfaces (abstract classes used via implements) to define capabilities or contracts that unrelated types can fulfill. Dart merges these concepts: an abstract class can serve as both.
// Abstract class: share code among related types
abstract class Repository<T> {
T? find(int id); // abstract
void save(T item) => print('saved'); // shared
}
// Interface: define a capability
abstract class Comparable<T> {
int compareTo(T other);
}
class Product extends Repository<Product>
implements Comparable<Product> {
@override
Product? find(int id) => null;
@override
int compareTo(Product other) => 0;
}Async/Await
async-Funktionen
async markiert eine asynchrone Funktion, gibt Future zurück
mixin Greeter {
String get name;
void greet() => print('Hello, $name!');
}
class User with Greeter {
@override
String name;
User(this.name);
}
User('Alice').greet(); // Hello, Alice!await
await kann nur in async-Funktionen verwendet werden
mixin Walker {
void walk() => print('walking');
}
mixin Talker {
void talk() => print('talking');
}
// mix in multiple mixins
class Person extends Object with Walker, Talker {
String name;
Person(this.name);
}
var p = Person('Bob');
p.walk(); // walking
p.talk(); // talkingtry-catch Async
Asynchrone Fehler werden mit try-catch abgefangen
mixin Musician on Performer {
void playNote() => print('playing note');
}
abstract class Performer {
void perform();
}
class Singer extends Performer with Musician {
@override
void perform() => print('singing');
}
Singer().playNote(); // playing note
// Musician can only be mixed into Performer subtypesFuture.wait Parallel
Mehrere Futures parallel ausführen
// 'mixin class' can be both extended and mixed in
mixin class Counter {
int _count = 0;
int get count => _count;
void increment() => _count++;
}
class App extends Counter {}
class Tool with Counter {}
print(App().count); // 0
App().increment();
Tool().increment();
print(App().count); // 0 (separate instance)async for-Schleife
await for konsumiert einen Stream
mixin A {
void hello() => print('A');
}
mixin B {
void hello() => print('B');
}
class X with A, B {}
class Y with B, A {}
X().hello(); // B (later mixin wins)
Y().hello(); // A (later mixin wins)
// resolution order: class -> last mixin -> ... -> first mixinStreams
Stream erstellen
async* erstellt einen Stream, yield gibt Werte aus
class Stack<T> {
final List<T> _items = [];
void push(T item) => _items.add(item);
T pop() => _items.removeLast();
bool get isEmpty => _items.isEmpty;
}
var s = Stack<int>();
s.push(1);
s.push(2);
print(s.pop()); // 2
var names = Stack<String>();
names.push('Al');Stream abhören
listen gibt ein StreamSubscription zurück
// generic method with its own type parameter
T firstOrDefault<T>(List<T> list, T defaultValue) {
return list.isEmpty ? defaultValue : list.first;
}
print(firstOrDefault<int>([1, 2, 3], 0)); // 1
print(firstOrDefault([], 'none')); // none (type inferred)
// type often inferred from argumentsStream-Methoden
Stream bietet verschiedene Komfortmethoden
class Comparable<T> {
int compareTo(T other);
}
// constrain T to subtypes of Comparable<T>
T max<T extends Comparable<T>>(T a, T b) {
return a.compareTo(b) >= 0 ? a : b;
}
// number sum constraint
num sum<T extends num>(List<T> nums) =>
nums.fold(0, (a, b) => a + b);
print(sum([1, 2.5, 3])); // 6.5StreamController
Den Datenfluss des Streams manuell steuern
List<int> nums = [1, 2, 3];
Map<String, int> scores = {'a': 1, 'b': 2};
Set<double> uniq = {1.1, 2.2, 1.1};
// runtime type checks work (reified generics)
print(nums is List<int>); // true
print(nums is List<String>); // false
print(scores is Map); // true
// generic variance
void process(List<num> list) => print(list);
process(nums); // OK: int is a numStream-Transformation
Ähnlich wie verkettete Operationen von List
// generic function type alias
typedef Transformer<T> = T Function(T input);
int doubler(int x) => x * 2;
String upper(String s) => s.toUpperCase();
Transformer<int> dt = doubler;
Transformer<String> ut = upper;
print(dt(5)); // 10
print(ut('hi')); // HI
// generic class alias
typedef IntList = List<int>;
IntList xs = [1, 2, 3];Futures
Future erstellen
Future repräsentiert ein asynchrones Ergebnis
var nums = [3, 1, 2];
nums.sort();
print(nums); // [1, 2, 3]
print(nums.reversed.toList()); // [3, 2, 1]
print(nums.indexOf(2)); // 1
print(nums.contains(3));// true
print(nums.where((n) => n > 1).toList()); // [2, 3]
print(nums.map((n) => n * 2).toList()); // [2, 4, 6]
print(nums.fold(0, (a, b) => a + b)); // 6then-Verkettung
then gibt ein neues Future zurück
var a = {1, 2, 3};
var b = {2, 3, 4};
print(a.union(b)); // {1, 2, 3, 4}
print(a.intersection(b)); // {2, 3}
print(a.difference(b)); // {1}
var dedup = [1, 1, 2, 3, 3].toSet();
print(dedup.toList()); // [1, 2, 3]
print(dedup.contains(2)); // trueFuture.delayed
Verzögerte Ausführung
var ages = {'Alice': 30, 'Bob': 25};
ages['Carol'] = 28;
print(ages.keys); // (Alice, Bob, Carol)
print(ages.values); // (30, 25, 28)
print(ages.length); // 3
ages.update('Bob', (v) => v + 1);
print(ages['Bob']); // 26
ages.remove('Alice');
ages.forEach((k, v) => print('$k=$v'));
var mapped = ages.map((k, v) => MapEntry(k, v + 100));Future.any
Gibt das Ergebnis des ersten abgeschlossenen Futures zurück
var a = [1, 2];
var b = [0, ...a, 3]; // [0, 1, 2, 3]
print(b);
List<int>? maybe;
var c = [0, ...?maybe, 4]; // [0, 4] (null-spread is safe)
print(c);
var m1 = {'a': 1};
var m2 = {'b': 2, ...m1}; // {b: 2, a: 1}
print(m2);Completer
Ein Future manuell abschließen
var promo = true;
var menu = [
'home',
'products',
if (promo) 'sale',
'about',
];
print(menu); // [home, products, sale, about]
var nums = [1, 2, 3];
var doubled = [
for (var n in nums) n * 2,
];
print(doubled); // [2, 4, 6]
// combine: [for (var x in xs) if (x > 0) x]Higher-order Methods
Lists/Iterables support any, every, firstWhere, reduce, fold, skip, take, expand, and more. fold is powerful—it carries an accumulator of any type. These higher-order methods enable declarative, functional-style data processing without explicit loops.
var nums = [1, 2, 3, 4, 5];
print(nums.any((n) => n > 4)); // true
print(nums.every((n) => n > 0)); // true
print(nums.firstWhere((n) => n > 2)); // 3
print(nums.reduce((a, b) => a + b)); // 15
var byParity = nums.fold(<bool, List<int>>{}, (m, n) {
m[n.isOdd] = [...?m[n.isOdd], n]; return m;
});
print(byParity); // {true: [1,3,5], false: [2,4]}Collections
List-Operationen
List ist eine geordnete Sammlung, die Duplikate erlaubt
Future<String> fetchUser() {
return Future.delayed(Duration(seconds: 1), () => 'Alice');
}
void main() {
fetchUser().then((name) {
print('Got: $name'); // Got: Alice (after 1s)
});
print('waiting...');
}Set-Operationen
Set ist eine ungeordnete Sammlung eindeutiger Elemente
Future<String> fetchUser() async {
await Future.delayed(Duration(seconds: 1));
return 'Alice';
}
Future<void> main() async {
print('start');
String name = await fetchUser();
print('Got: $name');
print('done');
}Map-Operationen
Map ist eine Schlüssel-Wert-Paar-Sammlung
Future<int> divide(int a, int b) async {
if (b == 0) throw Exception('divide by zero');
return a ~/ b;
}
Future<void> main() async {
try {
var result = await divide(10, 0);
print(result);
} catch (e) {
print('Error: $e'); // Error: Exception: divide by zero
} finally {
print('done');
}
}Collection Spread
... Spread-Operator
Future<int> compute() async => 42;
compute()
.then((v) => v * 2)
.then((v) => print(v)) // 84
.catchError((e) => print('err: $e'))
.whenComplete(() => print('cleanup'));
// chaining transforms the result type
Future<String> fetch() async => 'data';
fetch().then((s) => s.length).then(print); // 4collection-if und collection-for
Dart-spezifische Bedingung/Schleife innerhalb von Collections
Future<int> task(int n) async {
await Future.delayed(Duration(milliseconds: n));
return n;
}
// run in parallel, wait for all
var all = await Future.wait([task(100), task(50), task(200)]);
print(all); // [100, 50, 200]
// resolve with the first to complete
var first = await Future.any([task(100), task(50), task(200)]);
print(first); // 50 (fastest)Completer
A Completer lets you manually create and complete a Future. Call complete(value) or completeError(error) to finish it. Completers are useful when wrapping callback-based APIs into Futures, or when a Future's completion is triggered by an external event you control.
import 'dart:async';
Completer<String> completer = Completer<String>();
// complete the future from elsewhere
Future<String> get value => completer.future;
completer.complete('resolved!');
void main() async {
print(await value); // resolved!
}
// useful when bridging callback-based APIs to FuturesString-Methoden
String-Interpolation
$variable oder ${expression}
Stream<int> countDown(int from) async* {
while (from > 0) {
await Future.delayed(Duration(seconds: 1));
yield from;
from--;
}
}
void main() async {
await for (var n in countDown(3)) {
print(n); // 3, 2, 1 (one per second)
}
}Häufige Methoden
Strings sind unveränderlich
var sub = countDown(3).listen(
(n) => print('got $n'),
onDone: () => print('done'),
onError: (e) => print('err: $e'),
);
// pause/resume/cancel
sub.pause();
sub.resume();
// sub.cancel(); // stop listening
Stream<int> countDown(int from) async* {
while (from > 0) yield from--;
}Teilstrings
Index beginnt bei 0
var stream = Stream.fromIterable([1, 2, 3, 4]);
// transform like an Iterable
var evens = stream.where((n) => n.isEven);
var doubled = stream.map((n) => n * 2);
await for (var n in Stream.fromIterable([1,2,3]).map((n) => n * 10)) {
print(n); // 10, 20, 30
}
print(await stream.first); // 1
print(await stream.last); // 4
print(await stream.length); // 4Ersetzen und Teilen
Unterstützt Regex-Ersetzung
import 'dart:async';
var controller = StreamController<int>();
// add events manually
controller.add(1);
controller.add(2);
controller.addError('oops');
controller.close();
controller.stream.listen(
(n) => print(n), // 1, 2
onError: (e) => print(e), // oops
onDone: () => print('done'),
);
// use controller.addError/sink.add for errorsStringBuilder
StringBuffer für häufige Verkettung verwenden
// await for consumes a stream like a loop
Future<int> sumStream(Stream<int> s) async {
var total = 0;
await for (var n in s) {
total += n;
}
return total;
}
print(await sumStream(Stream.fromIterable([1, 2, 3]))); // 6
// broadcast stream: multiple listeners
var bc = StreamController<int>.broadcast();
bc.stream.listen(print);
bc.stream.listen((n) => print('got $n'));
bc.add(5); // both listeners receive 5Ausnahmebehandlung
throw
Kann jedes Objekt werfen
void checkAge(int age) {
if (age < 0) {
throw ArgumentError('age cannot be negative');
}
if (age > 150) {
throw StateError('unrealistic age: $age');
}
}
// you can throw any non-null object
void fail() => throw 'something went wrong';try-catch-finally
on fängt bestimmte Ausnahmetypen ab
try {
checkAge(-5);
} on ArgumentError catch (e) {
print('argument error: $e');
} on StateError catch (e) {
print('state error: $e');
} catch (e, stackTrace) {
print('unknown: $e');
print(stackTrace);
} finally {
print('always runs');
}Benutzerdefinierte Ausnahmen
Exception-Schnittstelle implementieren
class InvalidCredentialsException implements Exception {
final String message;
InvalidCredentialsException(this.message);
@override
String toString() => 'InvalidCredentialsException: $message';
}
void login(String user, String pass) {
if (user.isEmpty) {
throw InvalidCredentialsException('username required');
}
}
try {
login('', 'x');
} on InvalidCredentialsException catch (e) {
print(e); // InvalidCredentialsException: username required
}rethrow
rethrow wirft die Ausnahme erneut
Future<void> logErrors(Future<void> Function() action) async {
try {
await action();
} catch (e) {
print('logging error: $e');
rethrow; // re-throw the caught exception
}
}
void main() async {
try {
await logErrors(() async => throw Exception('fail'));
} catch (e) {
print('handled upstream: $e');
}
}Error vs Exception
Exception is for runtime conditions a program can reasonably catch and recover from (e.g., network failure, bad input). Error represents programming bugs (type errors, assertion failures, index out of range) that should be fixed in code, not caught at runtime. Catching Errors is discouraged.
// Exception: recoverable, expected to be caught
class MyException implements Exception {}
// Error: programming bugs, not meant to be caught
class MyError extends Error {
@override
String toString() => 'MyError: invalid state';
}
void risky() {
throw MyError(); // bug: should fix the code
throw MyException(); // runtime condition: catch it
}
// assert failures, type errors, range errors are ErrorsEnums
Basis-Enum
Enum-Werte haben name und index
// alias for a function type
typedef IntOperator = int Function(int, int);
int add(int a, int b) => a + b;
int mul(int a, int b) => a * b;
IntOperator op = add;
print(op(2, 3)); // 5
op = mul;
print(op(2, 3)); // 6
// pass as a parameter
void apply(IntOperator f, int a, int b) => print(f(a, b));Erweitertes Enum (Dart 3)
Enums können Felder und Methoden haben
// generic function type alias
typedef Mapper<T, R> = R Function(T input);
String stringify(int n) => n.toString();
int lenOf(String s) => s.length;
Mapper<int, String> intToStr = stringify;
Mapper<String, int> strToLen = lenOf;
print(intToStr(42)); // '42'
print(strToLen('hi')); // 2Enums iterieren
values gibt alle Enum-Werte zurück
// Dart 2.13+: alias for any type, not just functions
typedef IntList = List<int>;
typedef StringMap<V> = Map<String, V>;
IntList nums = [1, 2, 3];
StringMap<int> scores = {'a': 1};
// alias for a record type (Dart 3)
typedef Point = ({double x, double y});
Point p = (x: 1.0, y: 2.0);
print(p.x); // 1.0switch Enum
Dart 3 benötigt kein break
typedef Predicate<T> = bool Function(T);
bool isEven(int n) => n.isEven;
List<T> filter<T>(List<T> list, Predicate<T> test) {
return list.where(test).toList();
}
print(filter([1, 2, 3, 4], isEven)); // [2, 4]
print(filter(['', 'a', ''], (s) => s.isNotEmpty)); // [a]
// typedef makes callback contracts explicittypedef vs inline Function types
A typedef is just an alias—it's identical to the inline function type at runtime and for type checking. typedef improves readability and centralizes the contract so changes happen in one place. Prefer typedef for any function type used in more than one location.
// these two are equivalent
typedef Handler = void Function(String event);
class EventBus {
// using typedef
void on(Handler handler) {}
// using inline type
void on2(void Function(String) handler) {}
}
// both accept the same functions
void myHandler(String e) => print(e);
EventBus().on(myHandler);
EventBus().on2(myHandler);Generics
Generische Klasse
T ist ein Typparameter
enum Color { red, green, blue }
var c = Color.red;
print(c); // Color.red
print(c.name); // 'red'
print(c.index); // 0
print(Color.values); // [Color.red, Color.green, Color.blue]
print(Color.green.index); // 1Generische Methoden
Methoden können auch Typparameter haben
enum Vehicle {
car('Car', 4),
bike('Bike', 2),
truck('Truck', 6);
final String label;
final int wheels;
const Vehicle(this.label, this.wheels);
int get axles => wheels ~/ 2;
}
print(Vehicle.car.label); // Car
print(Vehicle.bike.wheels); // 2
print(Vehicle.truck.axles); // 3Generische Einschränkungen
extends beschränkt den Typparameter
enum Status { pending, active, done }
// iterate all values
for (var s in Status.values) {
print(s.name);
}
// exhaustive switch (no default needed)
String label(Status s) => switch (s) {
Status.pending => 'Waiting',
Status.active => 'Running',
Status.done => 'Finished',
};
print(label(Status.active)); // RunningGenerische Collections
Collections verwenden Generics umfangreich
enum HttpStatus {
ok(200),
notFound(404),
serverError(500);
final int code;
const HttpStatus(this.code);
bool get isSuccess => code >= 200 && code < 300;
String get reason => switch (this) {
ok => 'OK',
notFound => 'Not Found',
serverError => 'Internal Server Error',
};
}
print(HttpStatus.ok.isSuccess); // true
print(HttpStatus.notFound.reason); // Not FoundEnum Comparison
Each enum value is a singleton—there's exactly one instance per value per program. == compares by identity effectively. index allows ordering by declaration position. Use == for equality; identical() also works since values are canonicalized. Enums make great Map keys and Set elements.
enum Priority { low, medium, high }
var a = Priority.low;
var b = Priority.high;
print(a == b); // false
print(a == Priority.low);// true
print(a.index < b.index);// true
print(identical(a, Priority.low)); // true
// enums are singletons: only one instance per value
// use == for equality, not identical (though both work)Typdefinitionen
Funktionstyp-Alias
Erstellt einen Alias für einen Funktionstyp
class Animal {
@override
String toString() => 'Animal';
@Deprecated('use newName instead')
String oldName = 'x';
String newName = 'x';
@protected
void internalMethod() {}
@visibleForTesting
String testHook() => 'test';
}Generisches typedef
Unterstützt generische Parameter
class Base {
void greet() {}
String name = 'base';
}
class Derived extends Base {
@override
void greet() => print('hi');
// @override verifies the parent method exists
// typo here would be a compile error:
// @override void greeet() {}
}
// @Deprecated emits a warning at the call site
@Deprecated('use bar()')
void foo() {}
void bar() {}Neuartiges typedef
Dart 2.13+ unterstützt Nicht-Funktions-Typ-Aliase
// a custom annotation is just a const constructor class
class Todo {
final String msg;
const Todo(this.msg);
}
class Service {
@Todo('refactor to use cache')
void fetchData() {}
@Todo('add tests')
void process() {}
}
// annotations are accessed via dart:mirrors (VM) or
// code generation (build_runner) in practice@immutable & @JsonSerializable
@immutable (from package:meta) marks a class whose instances should not change after construction; subclasses and fields should be final. @JsonSerializable (from json_serializable) triggers code generation for JSON conversion via build_runner. Annotations drive many Dart/Flutter ecosystems.
import 'package:meta/meta.dart';
// requires the 'meta' package
@immutable
class User {
final String name;
final int age;
const User(this.name, this.age);
}
// with package:json_annotation / json_serializable
// @JsonSerializable()
// class Product {
// final String id;
// Product(this.id);
// factory Product.fromJson(Map<String, dynamic> j) => ...;
// }Annotation on Parameters
Annotations can be placed on parameters, library declarations, and typedefs too. Before null safety, @required marked mandatory named params; the modern equivalent is the 'required' keyword. Annotations on parameters are widely used by serialization and DI frameworks.
class Required {
const Required([this.reason]);
final String? reason;
}
class Service {
// annotation on a parameter
void create({
@Required('name is mandatory') String? name,
@protected int? internal,
}) {
print(name);
}
}
// @required is built into Dart (the 'required' keyword
// is preferred in Dart 2.12+ for null safety)Null-Sicherheit
Nullbare Typen
? kennzeichnet einen nullbaren Typ
// import an entire library
import 'dart:io';
import 'package:http/http.dart';
// import only specific names
import 'dart:math' show Random, pi;
// hide specific names
import 'dart:async' hide Timer;
// prefix to avoid name clashes
import 'package:http/http.dart' as http;
http.get(Uri.parse('https://example.com'));Null-Assertion
! behauptet Nicht-Null, mit Vorsicht verwenden
// library.dart
library my_lib;
// split implementation across files
part 'src/widget_a.dart';
part 'src/widget_b.dart';
// re-export another library's API
export 'src/utils.dart' show formatDate, parseDate;
// users import library.dart and get everythingNull-sichere Operatoren
?. ?? ??= null-sichere Operationen
// names starting with _ are library-private
class _Internal {
void _helper() {}
}
class Public {
String _secret = 'hidden'; // private field
String name = 'visible'; // public field
String _process() => 'internal';
String reveal() => _process();
}
// _secret is accessible anywhere in the SAME library/file
// but not from other libraries that import this fileTyp-Heraufstufung
Automatische Typ-Heraufstufung nach Null-Prüfung
// load a library on demand (web only)
import 'package:heavy_lib/heavy.dart' deferred as heavy;
Future<void> main() async {
// library is NOT loaded until this call
await heavy.loadLibrary();
heavy.SomeClass().doWork();
}
// useful for splitting large web bundles and
// loading rarely-used features only when neededlate und null
late verzögert die Initialisierung nicht-nullbarer Variablen
// explicit library declaration
library my_package.utils;
import 'dart:math';
part 'src/helper.dart';
const double version = 1.0;
// a 'library' name is optional in modern Dart;
// it's mainly used with part/part-of and tooling
// most files omit it and are treated as anonymous librariespart & part of
part/part-of splits one library across files: the main file declares part 'file.dart'; the part file declares 'part of library;'. Parts share the library's scope including private (_name) members. Prefer separate libraries with export for new code—parts are for tightly-coupled implementations.
// shapes.dart
library shapes;
part 'circle.dart';
part 'square.dart';
class Shape {}
// circle.dart
part of shapes;
class Circle extends Shape {}
// files in the same library share private members
// (_name visible across all parts)Erweiterungen
Erweiterungsmethoden
Funktionalität zu bestehenden Typen hinzufügen
int a = 42; // non-nullable: cannot be null
int? b; // nullable: can be null
print(b); // null
b = 10;
print(b); // 10
String name = 'Al'; // non-nullable
String? middle; // nullable
// non-nullable types are guaranteed non-null
// int x = null; // ERROR
// print(a.length); // safe: a is non-nullErweiterungen verwenden
Wie eine reguläre Methode aufrufen
String? maybeName;
// int len = maybeName.length; // ERROR: maybeName is nullable
maybeName = 'Alice';
int len = maybeName!.length; // ! asserts non-null
print(len); // 5
// throws if null at runtime:
// String? n; print(n!.length); // NoSuchMethodError/nullGenerische Erweiterungen
Unterstützt generische Typparameter
String? name;
print(name?.length); // null (safe access)
print(name?.length ?? 0);// 0 (default if null)
name = 'Alice';
print(name?.length); // 5
name ??= 'Bob'; // assign only if null
print(name); // Alice (already set)
List<int>? list;
print(list?.first); // null
print(list?.first ?? -1);// -1Type Promotion
Type promotion: after a null check (x != null) or type check (x is String), the compiler narrows the type within that branch—no explicit cast needed. Promotion also happens after a non-null assignment. Local variables promote well; fields may need explicit local copies.
String? name;
if (name != null) {
// name is promoted to non-nullable String here
print(name.length); // safe, no ! needed
}
// promoted via is check
Object obj = 'hello';
if (obj is String) {
print(obj.length); // smart-cast to String
}
// promoted via assignment
int? x;
x = 5;
print(x.abs()); // x is non-null after assignmentlate & required
late marks a non-nullable variable that will be initialized after declaration but before first use—deferring initialization. late final initializes once (lazily if given an initializer). required marks a named parameter as mandatory. Together they integrate cleanly with null safety.
class Config {
// late: non-nullable, initialized later
late final String value = _load();
// late without initializer: assign before first use
late final int computed;
Config() {
computed = expensive();
}
String _load() => 'loaded';
int expensive() => 42;
}
// required: mandatory named parameter
void build({required String name}) {}
build(name: 'Al'); // OK
// build(); // ERROR: missing requiredlate Lazy Initialization
A late field with an initializer is lazy—the initializer runs on first access, not at construction. The result is cached for subsequent accesses. This is great for expensive initialization, circular references, and fields that depend on 'this' being fully constructed. late final makes it a one-time computation.
class Service {
// lazy: _expensive runs only on first access
late final int cache = _expensive();
int _expensive() {
print('computing...');
return 42;
}
}
var s = Service();
print('created');
print(s.cache); // computing... 42
print(s.cache); // 42 (cached, no recompute)
// late fields with initializers are evaluated lazilyVerwandte Dart-Snippets
Copy-paste ready code for common tasks.
Klassen und Konstruktoren
Klassen mit benannten Konstruktoren und Factory definieren.
Async/Await und Futures
Asynchrone Programmierung mit Future und async/await.
Collections (List, Map, Set)
Mit Collections und funktionalen Operationen arbeiten.
Null Safety
Solide Null Safety mit ?- und !-Operatoren.
Generics
Wiederverwendbare, typsichere Klassen und Methoden.
Mixins und Extensions
Verhalten ohne Vererbung komponieren.
Futures und Streams
Mit einzelnen und mehreren asynchronen Werten arbeiten.
Isolates (Echte Parallelität)
Code in separaten Isolaten für CPU-intensive Arbeit ausführen.
Was this helpful?