Open-source libraries used

1 libraries are bundled into this tool's code.

Dart Cheatsheet — Concise Reference

A cheatsheet for Dart 3 syntax, type system, async and the most-used standard libraries — covering ~80% of daily work.

D

Dart Dart 3

Dart SDK · OO · Generic · Functional · Async-first · Static (sound null safety)

Recommended Learning Path

Start with dart create/run and the pubspec layout → grasp variables, types and control flow → dig into functions, strings and collections → understand classes, mixin and error handling → write concurrent code with isolates and async/await → then learn networking, time, processes, regex, and build & test as needed. The FAQ chapter is your go-to for avoiding pitfalls later.

1.Hello World & Runtime

Minimal program, dart create/run, and project structure.

Minimal program

Every Dart program starts from a void main() entry function. dart run executes it, and print outputs to stdout.

1
2
3
4
5
6
7
8
9
void main() {
print('Hello, world!');
}
// How to run:
// dart run hello.dart
// Or compile first, then execute:
// dart compile exe hello.dart -o hello
// main must be named main to be the entry point
// Return type void means no value is returned

Create & run

dart create scaffolds a standard project; dart run executes the entry file directly. Use JIT for fast iteration during development and AOT for efficient release builds.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Create a console project:
// dart create -t console myapp
// Create a Flutter project:
// flutter create myapp
// Enter the project directory:
// cd myapp
// Run the entry file:
// dart run
// Run a specific file:
// dart run bin/main.dart
// Compile to an executable:
// dart compile exe bin/main.dart
// Static analysis:
// dart analyze

Project structure

pubspec.yaml declares the package name, dependencies and SDK constraint. Code lives in lib/ and bin/, tests in test/.

1
2
3
4
5
6
7
8
9
10
11
12
// pubspec.yaml example:
// name: myapp
// environment:
// sdk: '>=3.0.0 <4.0.0'
// dependencies:
// http: ^1.1.0
// Directory conventions:
// lib/main.dart library code
// bin/main.dart executable entry
// test/ unit tests
// Install dependencies:
// dart pub get

Print output

print writes to stdout and appends a newline. Use $ to interpolate a variable, ${expr} for expressions.

1
2
3
4
5
6
7
8
9
10
void main() {
print('Hello, Dart!');
final name = 'Rex';
print('Hi, $name'); // variable interpolation
final age = 5;
print('Age is ${age + 1}'); // expression interpolation
print('Pi = ${3.14159.toStringAsFixed(2)}');
}
// stdout.write does not add a newline
// stderr.writeln writes to the error stream

Command-line arguments

main can accept a List<String> args parameter. args[0] is the first positional argument; the program name is not included.

1
2
3
4
5
6
7
8
9
10
11
void main(List<String> args) {
print('参数个数: ${args.length}');
for (final arg in args) {
print('参数: $arg');
}
}
// Run:
// dart run hello.dart a b c
// args == ['a', 'b', 'c']
// The program name itself is not in args
// Use Platform.script when you need the program path

Async main function

main may return Future<void>; the program waits for all awaits to finish before exiting — useful for time-consuming work.

1
2
3
4
5
6
7
8
9
Future<void> main() async {
print('开始');
await Future.delayed(Duration(seconds: 1));
print('一秒后执行');
}
// Future.delayed simulates a long-running task
// await blocks until the Future completes
// Returning Future<void> makes the runtime wait
// Without awaiting, the program may exit early

Comments

Use // for single-line, /// for documentation (used by dart doc), and /* */ for block comments. Comments don't affect execution.

1
2
3
4
5
6
7
8
9
// Single-line comment
/// Doc comment: used by dart doc to generate API docs
/* Block comment: can be closed on one line */
void main() {
// End-of-line comment
print('OK'); // explain what this line does
}
// Doc comments go before the declaration
// Use dart doc to generate HTML documentation

Top-level members

Dart allows top-level functions and variables (outside any class). import libraries to use their public API; top-level variables are visible across the file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import 'dart:math';
// Top-level variable:
final appName = 'demo';
// Top-level function:
int square(int x) => x * x;
void main() {
print(appName);
print(square(4));
print(max(1, 9));
}
// Top-level members are visible library-wide
// Prefer final for variables to avoid global mutability
// Private members start with an underscore: _helper
// Put imports at the very top of the file

2.Variables & Constants

Variable declarations, final/const, null safety, and destructuring.

var and final

var declares a variable that can be reassigned but its inferred type is fixed. final declares a variable that can only be assigned once.

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
var counter = 0; // inferred as int
counter = 1; // can be reassigned
final name = 'Rex'; // can be assigned only once
// name = 'New'; // error: final cannot change
final now = DateTime.now(); // determined at runtime
print('$counter $name $now');
}
// Once var infers a type, it is fixed
// A different type is an error: counter = 'x'
// final only constrains the number of assignments
// The object's internals can still be modified

const constants

const is a compile-time constant; the value must be known at compile time. const values are canonicalized — identical literals share one instance.

1
2
3
4
5
6
7
8
9
10
11
void main() {
const pi = 3.14159;
const greeting = 'Hello';
const list = [1, 2, 3]; // compile-time list
const double factor = 0.5;
print('$pi $greeting $list $factor');
}
// A const value must be known at compile time
// DateTime.now() cannot be used with const
// A const constructor creates a compile-time constant
// Prefer const to improve performance

Explicit types

You can annotate variables with explicit types. Static types help the compiler — annotate public APIs explicitly.

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
int count = 42;
double ratio = 3.14;
String name = 'Dart';
bool enabled = true;
List<int> nums = <int>[1, 2, 3];
print('$count $ratio $name $enabled $nums');
}
// The type goes before the variable name
// Explicit types make the intent clearer
// Local variables can use var and rely on inference
// Prefer writing types for parameters and return values

Nullable variables

Under sound null safety, types are non-nullable by default. Append ? to allow null; null-check or use ?? for a default value.

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
String? maybe = null; // nullable
String name = maybe ?? '匿名'; // null-coalescing default
if (maybe != null) {
print('有值: $maybe');
}
print(name);
}
// String? can be assigned null
// String cannot be assigned null (compile error)
// ?? uses the right side when the left is null
// After a null check Dart applies type promotion

late lazy initialization

late defers initialization until first access. late final allows only one assignment — perfect for fields that depend on runtime values.

1
2
3
4
5
6
7
8
9
10
11
12
class Config {
late final String apiKey = loadKey();
}
String loadKey() => 'abc-123';
void main() {
final config = Config();
print(config.apiKey); // loaded on first access
}
// late lets you skip assigning at declaration
// It must be assigned before first use or it throws
// late final guarantees it is initialized only once
// Common for singletons and lazily loaded config

Destructuring assignment

Dart 3 supports records and pattern destructuring — bind multiple values at once. The pattern must match the shape.

1
2
3
4
5
6
7
8
9
10
11
void main() {
final (x, y) = (10, 20);
final (name: n, age: a) = (name: 'Rex', age: 5);
final [first, second, ...] = [1, 2, 3];
print('$x $y $n $a $first $second');
}
// Ignore extra elements with _:
// final [_, second2] = [1, 2];
// Without a rest element the list pattern length must match
// It also works for map keys and values
// for (final MapEntry(k: k, v: v) in entries)

Scope & shadowing

A variable is visible within its block; inner scopes may shadow outer ones. Top-level variables are visible across the library.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
final top = '全局';
void main() {
final inner = '函数内';
if (true) {
final block = '块内';
print(block);
print(inner); // outer scope is visible
print(top); // global is visible
}
// print(block); // error: out of scope
}
// Block scope matches the curly braces
// An inner scope can shadow an outer name
// Redeclaring in the same scope is an error
// Avoid deep shadowing to keep code readable

dynamic and Object

dynamic bypasses static checks; the type is resolved at runtime. Object is the root of the type hierarchy and requires explicit casts.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void main() {
dynamic d = 42;
d = '变成字符串'; // any type at runtime
Object obj = 42;
// obj.length; // error: Object has no such member
if (obj is String) {
print(obj.length); // accessible after promotion
}
print(d);
}
// dynamic skips static checking, use it sparingly
// Object is the supertype of all classes
// An is check triggers type promotion
// Prefer explicit types or generics

3.Data Types

Built-in types, collection types, records, and enums.

Numeric types

int is an integer; double is a double-precision float; num is their supertype. An int literal is assignable to double.

1
2
3
4
5
6
7
8
9
10
11
void main() {
int i = 42;
double d = 3.14;
num n = 42; // num can hold integers
double whole = 42; // int is assignable to double
print('$i $d $n $whole');
}
// int is 64-bit on native platforms
// double is 64-bit IEEE 754
// The result type depends on the operands
// Check the type with is: if (n is int)

String type

String is an immutable sequence of UTF-16 code units. Single and double quotes are equivalent; interpolation and multiline literals are supported.

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
String a = '单引号';
String b = "双引号"; // equivalent
String multi = '''第一行
第二行''';
print('$a $b');
print(multi);
}
// Strings are immutable
// Modifying operations all return a new string
// One quote style can contain the other directly
// Escape with a backslash when you need the same quote

Boolean type

bool has only true and false. Conditional expressions must return bool — there is no implicit numeric conversion.

1
2
3
4
5
6
7
8
9
10
11
void main() {
bool ok = true;
bool done = false;
if (ok) print('成功');
final result = ok && !done;
print(result);
}
// && is logical AND, || is logical OR
// ! is logical NOT
// Ternary: ok ? '是' : '否'
// Dart has no truthy/falsy concept

List type

List is an ordered, growable collection supporting generics. Literal [a, b] creates a list, growable by default.

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
List<int> nums = [1, 2, 3];
nums.add(4);
nums.remove(2);
final first = nums.first;
final len = nums.length;
print('$first $len $nums');
}
// List<String> restricts the element type via generics
// Index access nums[0] throws when out of range
// isEmpty / isNotEmpty check emptiness
// first / last get the first and last elements

Set and Map

Set is an unordered, unique-element collection; Map is a key-value collection. Both are generic and have literal syntax.

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
Set<String> tags = {'a', 'b', 'a'}; // duplicates removed
tags.add('c');
Map<String, int> ages = {'alice': 30};
ages['bob'] = 25;
print(tags); // {a, b, c}
print(ages['bob']); // 25
print(ages['nobody']); // null
}
// Use contains to test Set membership
// Reading a missing Map key returns null
// Map iteration follows insertion order
// Set is ideal for dedup and membership tests

Record type

Dart 3 Records are unnamed, lightweight aggregates. They can have named fields and work with pattern destructuring.

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
var point = (x: 1, y: 2); // named record
var pair = ('left', 3); // positional record
print(point.x);
print(pair.$1); // positional access
final (a, b) = ('first', 2);
print('$a $b');
}
// Records get == and hashCode automatically
// A record's type is its shape: (int, int)
// Good for returning multiple values instead of tiny classes
// Records are immutable

Enums

enum defines a fixed set of named constants. Enhanced enums (Dart 2.17+) can carry fields and methods.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
enum Status { pending, approved, rejected }
enum Color {
red(255), green(0), blue(0);
const Color(this.value);
final int value;
}
void main() {
final s = Status.approved;
print(s.name); // approved
print(Color.red.value);
for (final c in Status.values) {
print(c.name);
}
}
// .name gives the enum member name
// .values iterates all members
// switch over an enum is exhaustive by default
// Enhanced enums can carry data and behaviour

Type conversion

int.parse / double.parse convert a string to a number, toString converts back, and as performs a runtime cast.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void main() {
int n = int.parse('42');
double d = double.parse('3.14');
String s = 42.toString();
Object obj = 'hello';
if (obj is String) {
final len = obj.length; // safe after promotion
print(len);
}
print('$n $d $s');
}
// parse throws FormatException on failure
// toStringAsFixed(2) keeps two decimal places
// A failed as cast throws TypeError
// Prefer is + type promotion over as

4.References & Null Safety

Object references, null safety, deep copies, and native memory.

Object references

Dart has no raw pointers — variables hold references to objects. Assignment copies the reference; multiple variables can point to the same object.

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
final list1 = [1, 2, 3];
final list2 = list1; // copies the reference
list2.add(4);
print(list1); // [1, 2, 3, 4]
final copy = List.of(list1); // a real copy
copy.add(5);
print(list1); // unaffected
}
// Assigning a reference type shares the object
// List.of / [...list] make a shallow copy
// Primitive types are immutable value types
// Watch for side effects when sharing mutable objects

Null safety

Sound null safety restricts null to nullable types like String?. The compiler enforces non-null at compile time.

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
String name = 'Rex'; // non-nullable
String? nickname; // nullable, defaults to null
nickname = '小雷';
if (nickname != null) {
print('$name 的昵称 $nickname');
}
print(nickname);
}
// Assigning null to a non-nullable type is a compile error
// After a null check the variable is promoted to non-null
// ?? supplies a default so you can skip the null check
// ?. is safe access: nickname?.length

Null-assertion !

Appending ! asserts that a nullable expression is non-null; it throws a null check error if violated. Use it only when you're sure.

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
String? maybe = '有值';
final len = maybe!.length; // assert non-null
print(len);
String? empty;
final safe = empty?.length; // null
print(safe);
}
// ! bypasses the null-safety check
// A failed assertion throws a Null check error
// Prefer null checks and type promotion
// ?. returns null instead of throwing

Null-coalescing

?? yields the right-hand side when the left is null. ??= assigns only if the variable is currently null.

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
String? user;
final name = user ?? '匿名'; // null-coalescing
user ??= '游客'; // assign only when null
int? count;
count ??= 0;
count++;
print('$name $user $count');
}
// ?? only cares whether the left side is null
// ??= is equivalent to count = count ?? 0
// Can be chained: a ?? b ?? c ?? '默认'
// ?? also gives collection defaults: list ?? []

late and const

final assigns once at runtime; const is a compile-time constant. late defers initialization — useful for expensive objects and circular dependencies.

1
2
3
4
5
6
7
8
9
10
11
void main() {
final now = DateTime.now(); // runtime
const pi = 3.14159; // compile time
late final big = createBig(); // on first access
print('$now $pi ${big.length}');
}
List<int> createBig() => List<int>.generate(100, (i) => i);
// final can be assigned in a constructor or at runtime
// A const value must be determinable at compile time
// late defers execution until first access
// Top-level and static variables are lazily initialized

References and equality

== defaults to reference comparison; identical() checks for the same instance. Use a helper for structural equality.

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
final a = [1, 2];
final b = [1, 2];
print(a == b); // false: different objects
print(identical(a, b)); // false: different references
final c = a;
print(identical(a, c)); // true: same reference
print(a.length == b.length); // true
}
// Strings and numbers have value semantics, == compares content
// List/Map compare by reference by default
// Use listEquals (package:collection) for structural comparison
// Custom classes can override == and hashCode

Deep vs shallow copy

A shallow copy duplicates only the top level — nested objects are still shared. Deep copy repeats per layer; immutable collections are safe to share.

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
final nested = [[1], [2]];
final shallow = List.of(nested); // shallow copy
shallow[0].add(9);
print(nested[0]); // [1, 9]
final deep = nested.map((e) => List.of(e)).toList();
deep[0].add(8);
print(nested[0]); // [1, 9] unchanged
}
// A shallow copy shares the nested objects
// A deep copy creates new objects at every level
// Map.of / Set.of work the same way
// Immutable collections avoid shared side effects

Native memory

dart:ffi exposes Pointer and calloc for native memory access — used for C interop. Pointer lifetimes are the developer's responsibility.

1
2
3
4
5
6
7
8
9
10
11
12
// dart:ffi provides native memory access:
// Pointer<Int32> represents a C int pointer
// Allocate and free:
// final p = calloc<Int32>(1); // package:ffi
// p.value = 42;
// calloc.free(p);
// Interoperate with C libraries:
// final lib = DynamicLibrary.open('libc.so')
// final fn = lib.lookupFunction(...)
// Strings use Pointer<Utf8>
// Native platforms only, not available on the Web
// Pointer lifetimes are the developer's responsibility

5.Control Flow

if, loops, switch, and pattern matching.

if / else

if/else if/else branches on a condition, which must be bool. Null-checks trigger type promotion.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void main() {
int score = 85;
if (score >= 90) {
print('优秀');
} else if (score >= 60) {
print('及格');
} else {
print('不及格');
}
}
// The condition must evaluate to bool
// Braces are optional but recommended
// Type promotion happens after a null check:
// if (name != null) print(name.length)
// Without else, an unmet condition is simply skipped

Ternary expression

cond ? then : else is a single-expression branch. Combine with ?? for null defaults.

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
final n = 7;
final sign = n > 0 ? '正' : '非正';
String? maybe;
final label = maybe ?? '默认'; // null-coalescing
print(sign);
print(label);
}
// Both ternary branches should have the same type
// Nested ternaries hurt readability, use them sparingly
// ?? is dedicated to null defaults
// if/else is clearer for complex branching

for loop

Classic three-part for; for-in iterates an Iterable. Dart 3 supports pattern destructuring in the loop variable.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void main() {
for (var i = 0; i < 3; i++) {
print('i=$i');
}
final names = ['Rex', 'Ada'];
for (final name in names) {
print(name);
}
for (final (i, name) in names.indexed) {
print('$i: $name');
}
}
// i++ is equivalent to i = i + 1
// for-in iterates any Iterable and Map
// .indexed yields (index, value) records
// Be careful when modifying the collection being iterated

while loop

while checks then runs; do-while runs once then checks. Use them for loops of unknown iteration count.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void main() {
var n = 3;
while (n > 0) {
print('n=$n');
n--;
}
var count = 0;
do {
print('至少执行一次');
count++;
} while (count < 1);
}
// while never runs if the condition is false
// do-while runs at least once
// The condition must be a bool
// Remember to update the loop variable to avoid infinite loops

switch expression

Dart 3 switch expression returns a value. Each case has a pattern; use => to produce the result — no break needed.

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
final status = 404;
final label = switch (status) {
200 => '成功',
404 => '未找到',
_ => '其他',
};
print(label);
}
// It must be exhaustive or end with _ as a fallback
// The expression form uses => instead of :
// Patterns are supported: switch (value) { int n => ... }
// More concise than an if chain and the result is assignable

switch statement

switch statements branch on cases — empty cases fall through, guards and patterns are supported. Each non-empty case must break.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void main() {
final day = 3;
switch (day) {
case 1:
case 2:
case 3:
print('工作日');
break;
default:
print('周末');
}
switch (day) {
case int n when n > 3:
print('大于 3');
break;
default:
print('其他');
}
}
// Empty cases fall through automatically
// Non-empty branches need break or return
// when adds an extra guard condition
// Pattern matching is more powerful than an if chain

Pattern matching

if-case and switch perform structural matching. Combine object patterns, record patterns and guards.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Circle {
const Circle(this.radius);
final double radius;
}
void main() {
final shape = Circle(5);
if (shape case Circle(radius: final r)) {
print('半径 $r');
}
final value = (name: 'Rex', age: 5);
final msg = switch (value) {
(name: final n, age: 0) => '$n 刚出生',
(name: final n, age: final a) => '$n $a 岁',
_ => '未知',
};
print(msg);
}
// if-case enters the branch only when the match succeeds
// Object patterns destructure by field name
// Record patterns match by position or by name
// An exhaustive switch lets the compiler guarantee coverage

break and continue

break exits the loop; continue skips to the next iteration. Labels control jumps in nested loops.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void main() {
for (var i = 0; i < 5; i++) {
if (i == 1) continue; // skip i=1
if (i == 4) break; // exit the loop
print('i=$i'); // 0 2 3
}
outer:
for (var i = 0; i < 3; i++) {
for (var j = 0; j < 3; j++) {
if (j == 1) break outer; // exit the outer loop
print('$i,$j');
}
}
}
// continue skips only the rest of this iteration
// break ends the loop entirely
// A label goes before the loop and is referenced by name
// Labels are handy for breaking out of deep nesting

6.Functions & Lambdas

Function definitions, optional parameters, closures, and async functions.

Function definition

A function has a return type, name, parameters and body. Use void when there is no return value.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int add(int a, int b) {
return a + b;
}
void greet(String name) {
print('你好,$name');
}
void main() {
print(add(1, 2));
greet('Rex');
}
// The return type comes before the function name
// Parameters must have types
// Use void when there is no return value
// Functions are objects and can be assigned to variables

Arrow function

Single-expression bodies use => — the expression's value is returned. Great for pure functions and callbacks.

1
2
3
4
5
6
7
8
9
10
11
12
int square(int x) => x * x;
bool isEven(int n) => n % 2 == 0;
void main() {
print(square(4));
print(isEven(3));
final doubled = [1, 2, 3].map((n) => n * 2).toList();
print(doubled);
}
// The => body must be a single expression
// It is equivalent to { return expr; }
// Common in callbacks and higher-order functions
// void return: () => print('x')

Optional positional parameters

Square brackets [] mark optional positional parameters. Defaults (or null) are used when omitted — defaults must be compile-time constants.

1
2
3
4
5
6
7
8
9
10
11
12
String join(String a, [String b = '无', int times = 1]) {
return '$a $b 重复 $times 次';
}
void main() {
print(join('你好'));
print(join('你好', '世界'));
print(join('你好', '世界', 3));
}
// Parameters inside [] are optional and positional
// Write the default value after the parameter with =
// Without a default the type must be nullable
// Use ?? to supply a default for nullable optional parameters

Named parameters

Curly braces {} mark named parameters; pass them by name. Mark with required to force the caller to provide them.

1
2
3
4
5
6
7
8
9
10
11
12
13
String describe(String name,
{int age = 0, String? city, required bool married}) {
return '$name 年龄 $age 城市 $city 已婚 $married';
}
void main() {
final s = describe('Rex',
age: 5, city: '上海', married: false);
print(s);
}
// Named arguments are order-independent
// Omitted ones use the default value or are null
// required parameters must be passed explicitly
// Wrap long calls across lines for readability

Higher-order functions

Functions are first-class — pass them as arguments or return them. map / where / fold are the standard collection higher-order helpers.

1
2
3
4
5
6
7
8
9
10
11
void main() {
final nums = [1, 2, 3, 4];
final doubled = nums.map((n) => n * 2).toList();
final evens = nums.where((n) => n.isEven).toList();
final sum = nums.fold(0, (acc, n) => acc + n);
print('$doubled $evens $sum');
}
// map transforms element by element
// where filters by a condition
// fold accumulates into a single result
// The result is a lazy Iterable, use toList to materialize it

Closures

A closure captures the variables of its defining scope and can read and mutate them even after the outer function returns.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Function makeCounter() {
var count = 0;
return () => ++count;
}
void main() {
final counter = makeCounter();
print(counter()); // 1
print(counter()); // 2
final counter2 = makeCounter();
print(counter2()); // 1: independent state
}
// A closure captures its environment and state
// Each makeCounter call creates an independent closure
// Whether variables are shared or separate depends on where they live
// Often used to encapsulate state

Async functions

An async function returns a Future; inside, await pauses until the result is ready. await only works inside async functions.

1
2
3
4
5
6
7
8
9
10
11
12
13
Future<String> fetchData() async {
await Future.delayed(Duration(milliseconds: 100));
return '数据';
}
Future<void> main() async {
print('开始');
final data = await fetchData();
print(data);
}
// An async function implicitly returns Future<T>
// await suspends until the Future completes
// Errors propagate to the await site and can be caught with try/catch
// Forgetting await gives you the Future object itself

Generators

sync* yields a lazy Iterable; async* yields a Stream. yield emits one value; yield* delegates to another generator.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Iterable<int> countUp(int max) sync* {
for (var i = 0; i < max; i++) {
yield i;
}
}
Stream<int> streamCount(int max) async* {
for (var i = 0; i < max; i++) {
await Future.delayed(Duration(milliseconds: 10));
yield i;
}
}
void main() {
print(countUp(3).toList());
}
// sync* yields one at a time without holding memory
// async* pairs with await to produce a stream
// yield* expands a sub-sequence: yield* countUp(2)
// Generators run on demand, only on first access

7.Strings

Literals, interpolation, substrings, encoding, and formatting.

String literals

Single and double quotes both delimit strings. Escape with backslashes, or use a raw string with the r prefix.

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
String s1 = '单引号';
String s2 = "双引号";
String esc = '换行符\n制表符\t';
String raw = r'不转义\n直接显示';
print('$s1 $s2');
print(esc);
print(raw);
}
// \n newline \t tab \\ backslash
// r'...' is a raw string that ignores escapes
// Ideal for regular expressions and Windows paths
// Use the other quote style inside to avoid escaping

String interpolation

Use $var (or ${expr}) to embed a value in a string. Any object's toString is called automatically.

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
final name = 'Rex';
final age = 5;
print('$name 今年 $age 岁');
print('十年后 ${age + 10} 岁');
final list = [1, 2];
print('共 ${list.length} 个元素');
}
// $name inserts a variable directly
// ${...} inserts expressions and calls
// Objects are converted with toString automatically
// Clearer and more efficient than string concatenation

Multiline strings

Triple single quotes ''' start a multiline string — newlines and indentation are preserved.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void main() {
String poem = '''
床前明月光,
疑是地上霜。''';
print(poem);
String html = '''
<div>
<p>你好</p>
</div>''';
print(html);
}
// Multi-line strings keep the actual line breaks
// Leading and trailing newlines are included
// They can be combined with interpolation
// Ideal for SQL, HTML and template text

Common methods

contains / startsWith test for substrings; replaceAll swaps text; toUpperCase / toLowerCase change case.

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
final text = 'Hello Dart';
print(text.contains('Dart')); // true
print(text.startsWith('Hello')); // true
print(text.toLowerCase()); // hello dart
print(text.replaceAll('Dart', 'Flutter'));
print(text.replaceFirst('l', 'L')); // HeLlo Dart
print(text.length);
}
// String methods return a new string, the original is unchanged
// trim() removes leading and trailing whitespace
// isEmpty / isNotEmpty check emptiness
// Methods can be chained

Substrings & search

substring extracts a slice; indexOf locates a substring; split breaks on a delimiter into a list.

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
final text = 'hello world';
print(text.substring(0, 5)); // hello
print(text.substring(6)); // world
print(text.indexOf('world')); // 6
print(text.indexOf('x')); // -1 not found
print(text.split(' ')); // [hello, world]
}
// substring takes start (inclusive) and end (exclusive)
// indexOf returns -1 when not found
// lastIndexOf searches from the end
// split results do not contain the separator itself

Characters & encoding

Dart strings are UTF-16. codeUnits gives code units; runes gives code points — use runes to handle emojis.

1
2
3
4
5
6
7
8
9
10
11
void main() {
final s = 'A中😀';
print(s.length); // 4 UTF-16 units
print(s.codeUnits); // list of code units
print(s.runes.toList()); // list of code points
print(String.fromCharCodes(s.runes));
}
// Chinese characters and emoji take several UTF-16 units
// length returns code units, not characters
// runes returns Unicode code points
// Use runes.length when you need the real character count

Efficient concatenation

Repeated + concatenation allocates many temporaries. StringBuffer accumulates and produces the string in one go.

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
final buffer = StringBuffer();
for (var i = 0; i < 1000; i++) {
buffer.write('第 $i 行\n');
}
final result = buffer.toString();
print('${result.length} 个字符');
}
// write appends without a newline
// writeln appends and adds a newline
// toString produces the string in one go
// Far more efficient than concatenating with +

Formatting & parsing

toStringAsFixed sets decimal places; padLeft / padRight pad to a width; int.parse / double.parse parse numeric strings.

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
final pi = 3.14159;
print(pi.toStringAsFixed(2)); // 3.14
print(7.toString().padLeft(3, '0')); // 007
final n = int.parse('42');
final d = double.parse('3.14');
print('$n $d');
}
// toStringAsFixed rounds the value
// padLeft/padRight pad with characters
// parse throws FormatException on failure
// Specify a radix: int.parse('ff', radix: 16)

8.Collections

Lists, maps, set operations, and sorting.

List operations

add appends, insert places at index, remove deletes by value, sort orders ascending. Default List is growable and mutable.

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
final nums = [3, 1, 2];
nums.add(4); // [3, 1, 2, 4]
nums.insert(0, 0); // [0, 3, 1, 2, 4]
nums.remove(3); // remove the element 3
nums.sort(); // ascending
print(nums);
print(nums.reversed.toList());
}
// remove deletes the first equal element
// removeAt deletes by index
// contains checks for membership
// indexOf finds the first matching index

List higher-order

map transforms, where filters, reduce / fold aggregate, expand flattens. Most return a lazy Iterable.

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
final nums = [1, 2, 3, 4];
final doubled = nums.map((n) => n * 2).toList();
final evens = nums.where((n) => n.isEven).toList();
final sum = nums.reduce((a, b) => a + b);
final flat = [[1, 2], [3]].expand((e) => e).toList();
print('$doubled $evens $sum $flat');
}
// map/where return a lazy Iterable
// A lazy sequence is only computed while iterating
// reduce requires a non-empty list
// fold is safer because it takes an initial value

Spread operator

... spreads a collection's elements into a literal. ...? safely skips a null source.

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
final a = [1, 2];
final b = [3, 4];
final merged = [...a, ...b, 5]; // [1,2,3,4,5]
List<int>? maybe;
final safe = [...a, ...?maybe]; // null safe
final copy = [...a]; // shallow copy
print('$merged $safe $copy');
}
// The spread operator merges elements, not sub-lists
// ...? handles a null source automatically
// It also works in Map and Set literals
// Common for merging and copying collections

Collection if/for

Use if inside collection literals to include elements conditionally, for to generate many. Dart 3 also supports pattern destructuring here.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void main() {
final includeZero = true;
final nums = [
1,
if (includeZero) 0,
for (var i = 2; i < 4; i++) i,
];
print(nums); // [1, 0, 2, 3]
final pairs = {
for (var i = 0; i < 3; i++) 'k$i': i,
};
print(pairs);
}
// if omits the element when the condition fails
// for produces multiple elements from its body
// The same works for Set and Map
// It makes data descriptions more declarative

Map operations

Iterate Map keys / values / entries. putIfAbsent fills lazily, update mutates, remove deletes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void main() {
final ages = {'alice': 30, 'bob': 25};
ages['carol'] = 22; // insert
ages.update('bob', (v) => v + 1); // update
ages.putIfAbsent('dave', () => 40);
ages.remove('alice');
for (final entry in ages.entries) {
print('${entry.key}: ${entry.value}');
}
}
// Reading a missing key returns null
// putIfAbsent only computes when the key is missing
// entries gives you the key-value pairs
// containsKey tests whether a key exists

Set operations

Set enforces uniqueness. union / intersection / difference are set operations; toSet deduplicates a list.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void main() {
final a = {1, 2, 3};
final b = {3, 4, 5};
print(a.union(b)); // {1,2,3,4,5}
print(a.intersection(b)); // {3}
print(a.difference(b)); // {1,2}
final nums = [1, 1, 2, 3];
final unique = nums.toSet();
print(unique);
}
// A Set is unordered with unique elements
// contains checks membership efficiently
// Use a Set for dedup and membership checks
// Adding an existing element is a no-op

Sort & search

sort defaults to ascending; pass a comparator for custom order. indexOf does a linear search; contains checks membership.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void main() {
final names = ['bob', 'alice', 'carol'];
names.sort(); // alphabetical
print(names);
final people = [
('bob', 30),
('alice', 20),
];
people.sort((x, y) => x.$2.compareTo(y.$2));
print(people);
print(names.indexOf('bob'));
}
// The default order follows Comparable
// A comparator returns negative/zero/positive
// sort modifies the list in place
// Copy first if you need to keep the original order

Read-only collections

List.unmodifiable creates a read-only view — mutating throws UnsupportedError. List.of copies into a mutable list.

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
final frozen = List.unmodifiable([1, 2, 3]);
// frozen.add(4); // throws UnsupportedError
final editable = List.of([1, 2, 3]); // mutable copy
editable.add(4);
print(editable);
final constList = const [1, 2, 3]; // compile-time constant
print(frozen.length);
}
// unmodifiable is read-only at runtime
// const is a compile-time constant and canonicalized
// Read-only is safer when passing to an API
// Map.unmodifiable / Set.unmodifiable work the same way

9.Memory & Performance

Garbage collection, const canonicalization, and buffer reuse.

Garbage collection

The Dart VM garbage-collects automatically — no manual free. Objects with no references become eligible for collection.

1
2
3
4
5
6
7
8
9
10
void main() {
// An object becomes collectable once it loses all references:
var data = List<int>.generate(10000, (i) => i);
data = [1, 2, 3]; // the old list is no longer referenced
print(data);
}
// Unreferenced objects are cleaned up by the GC automatically
// You neither need nor can delete manually
// Short-lived objects are usually nothing to worry about
// Flutter uses a generational GC

const canonicalization

const values are canonicalized — identical literals share one instance. Prefer const on hot paths.

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
const a = [1, 2, 3];
const b = [1, 2, 3];
print(identical(a, b)); // true: same instance
final c = [1, 2, 3];
final d = [1, 2, 3];
print(identical(c, d)); // false
}
// Equal const values are canonicalized and shared
// Non-const literals create a new object each time
// Prefer const literals on hot paths
// Compile-time constants are never rebuilt

Lazy initialization

Top-level and static variables are lazily initialized on first access. late offers the same deferred semantics.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void main() {
print('开始');
print(cache); // first access triggers the computation
print(cache); // reuses the existing value
}
final cache = buildCache();
String buildCache() {
print('构建缓存中');
return 'heavy-data';
}
// A top-level variable initializes on first access
// Afterwards the same instance is reused
// late fields behave the same way
// Avoids unnecessary startup cost

List capacity

Growable lists resize as needed — repeated add reallocates. Pre-size when the count is known.

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
final nums = <int>[];
for (var i = 0; i < 100; i++) {
nums.add(i); // grows automatically
}
final sized = List<int>.filled(100, 0);
final growable = List<int>.generate(100, (i) => i);
print('${nums.length} ${sized.length} ${growable.length}');
}
// The default list is growable and resizing has a cost
// filled initializes a fixed length
// generate builds elements with a function
// Bulk construction is faster than frequent appends

Buffer reuse

Uint8List and other typed-data buffers suit binary work. Reuse buffers and use StringBuffer to avoid repeated allocation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import 'dart:typed_data';
void main() {
final bytes = Uint8List(4); // fixed size
bytes[0] = 255;
final buffer = StringBuffer();
for (var i = 0; i < 10; i++) {
buffer.write(i);
}
print('$bytes ${buffer.toString()}');
}
// Uint8List gives efficient byte-level access
// Use Uint8List instead of List<int> for large binaries
// Reusing one buffer reduces GC pressure
// Stream large data instead of loading it all at once

Weak references

Expando attaches data to an object without modifying it, using a weak key. WeakReference lets the object be collected.

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
final tags = Expando<String>();
final obj = Object();
tags[obj] = '元数据'; // attach data
print(tags[obj]); // 元数据
final weak = WeakReference(obj);
print(weak.target == obj); // true
}
// Expando keys are weak references
// The attached data disappears once the object is collected
// Good for caches, debugging and framework extensions
// It does not take up a field on the object itself

Object lifecycle

Objects live in their isolate's heap. Isolates don't share mutable state — only copies or messages.

1
2
3
4
5
6
7
8
9
10
11
void main() {
// Each isolate has its own heap
final local = [1, 2, 3];
// What you send to another isolate is a copy:
// final result = await Isolate.run(() => local);
print(local);
}
// Isolates do not share memory
// Shared state requires message passing or ports
// Globally mutable objects are dangerous, use them sparingly
// Ideal for self-contained data-processing tasks

Native memory

Use dart:ffi for native memory during C interop. You must manually free — otherwise you leak.

1
2
3
4
5
6
7
8
9
10
import 'dart:ffi';
// Declare a pointer type:
// typedef NativeBuf = Pointer<Uint8>;
// Allocate native memory:
// final p = calloc<Uint8>(1024); // package:ffi
// Use p[i] to read and write bytes
// Free it:
// calloc.free(p);
// Forgetting to free causes a leak
// ffi is available on native platforms only

10.Object-Oriented

Classes, inheritance, mixins, and interfaces.

Classes & constructors

class declares an object blueprint. Constructors share the class name; this. params assign directly to fields.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Animal {
String name;
int age;
Animal(this.name, this.age); // this-parameter assignment
void speak() {
print('$name 叫了一声');
}
}
void main() {
final dog = Animal('旺财', 3);
dog.speak();
print('${dog.name} ${dog.age} 岁');
}
// this.name assigns the constructor argument to the field
// Fields are non-nullable by default and must be initialized
// Methods can access instance fields
// Instantiation does not need the new keyword

Inheritance

extends inherits from a superclass; @override marks overrides; super calls parent members. Dart is single-inheritance.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Animal {
Animal(this.name);
final String name;
String sound() => '...';
}
class Dog extends Animal {
Dog(String name) : super(name);
@override
String sound() => '汪汪';
}
void main() {
final dog = Dog('旺财');
print(dog.sound());
}
// A subclass inherits the parent's fields and methods
// @override marks an overriding member
// super(name) forwards arguments to the parent constructor
// A class can have only one superclass

mixin

A mixin is a reusable behavior fragment; use with to apply it. More flexible than inheritance and avoids the single-parent limit.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mixin Swimmer {
void swim() => print('游泳');
}
mixin Flyer {
void fly() => print('飞行');
}
class Duck with Swimmer, Flyer {}
void main() {
final duck = Duck();
duck.swim();
duck.fly();
}
// The with keyword applies multiple mixins
// A mixin cannot be instantiated on its own
// Restrict the superclass: mixin X on BaseClass {}
// Composing behaviour beats deep inheritance

Abstract class

An abstract class can't be instantiated — it defines a contract that subclasses must fulfill.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
abstract class Shape {
double area();
}
class Circle extends Shape {
Circle(this.r);
final double r;
@override
double area() => 3.14159 * r * r;
}
void main() {
final c = Circle(2);
print(c.area().toStringAsFixed(2));
}
// An abstract method has no body
// An abstract class may contain concrete implementations
// implements can be used to implement the interface too
// Once unified you can use them through the parent type

Interfaces

Every class implicitly defines an interface — use implements to fulfill it. All members must be reimplemented.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Greeter {
String greet(String name) => '你好,$name';
}
class EnglishGreeter implements Greeter {
@override
String greet(String name) => 'Hello, $name';
}
void main() {
final g = EnglishGreeter();
print(g.greet('Rex'));
}
// implements does not inherit the implementation
// Every member of the interface must be overridden
// A class can implement multiple interfaces
// Use implements to express a type contract

Accessors

get defines a read-only accessor; set defines a writable one. Accessors read and write like fields without parentheses.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Rectangle {
Rectangle(this.width, this.height);
double width;
double height;
double get area => width * height;
set scale(double factor) {
width *= factor;
height *= factor;
}
}
void main() {
final r = Rectangle(2, 3);
print(r.area); // 6.0
r.scale = 2;
print(r.area); // 24.0
}
// get reads like a field
// set assigns like a field
// You can add validation logic inside get/set
// Use get for derived computed properties

Static members

static members belong to the class, not an instance. static methods can't access instance members.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MathUtils {
static const pi = 3.14159;
static int square(int x) => x * x;
static int _counter = 0; // private static
static int get count => _counter;
}
void main() {
print(MathUtils.pi);
print(MathUtils.square(5));
}
// Access static members directly through the class name
// static cannot access instance fields
// Private members start with an underscore, visible within the library
// static constants are common for shared configuration

sealed class

sealed classes restrict subclasses to the same library, so exhaustive switches need no default. A Dart 3 feature.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sealed class Shape {}
class Circle extends Shape {}
class Square extends Shape {}
String describe(Shape shape) => switch (shape) {
Circle() => '圆',
Square() => '方',
};
void main() {
print(describe(Circle()));
print(describe(Square()));
}
// sealed subclasses must be declared in the same library
// An exhaustive switch needs no default
// Adding a new subclass triggers a compile warning
// Very safe when combined with pattern matching

11.Error Handling

try/catch, custom exceptions, and async errors.

try / catch

try wraps code that may throw; catch handles exceptions. catch (e) gets the exception object.

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
try {
final n = int.parse('abc');
print(n);
} catch (e) {
print('解析失败: $e');
}
}
// catch (e) catches all exceptions
// after throw, remaining code in try is skipped
// uncaught exceptions crash the program
// catch (e, st) also gets stack trace st

on clause

on filters the caught exception type — pair with catch to read it. Stack handlers from top to bottom.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void main() {
try {
throw FormatException('格式错误');
} on FormatException catch (e) {
print('格式: ${e.message}');
} on ArgumentError {
print('参数错误');
} catch (e) {
print('其他: $e');
}
}
// `on Type` clause catches matching exceptions
// `on` clause without `catch` does not bind the exception object
// matching is in order; earlier clauses win
// fallback `catch` clause catches remaining exceptions

finally and rethrow

finally always runs, for cleanup. rethrow propagates the original error, preserving its stack.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void main() {
try {
outer();
} catch (e) {
print('最终处理: $e');
}
}
void outer() {
try {
throw Exception('原始错误');
} catch (e) {
print('中间捕获');
rethrow; // preserve stack and re-raise
} finally {
print('清理资源');
}
}
// finally always runs
// rethrow must be inside catch
// rethrow preserves the original stack
// commonly used to close resources and connections

Throw exceptions

throw raises an exception object. Anything can be thrown, but idiomatic code throws an Exception or Error subclass.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void main() {
try {
checkAge(-1);
} catch (e) {
print('拒绝: $e');
}
}
void checkAge(int age) {
if (age < 0) {
throw ArgumentError('年龄不能为负');
}
}
// throw interrupts current control flow
// an exception can be any object
// by convention use Exception or Error
// recommend throwing for invalid business conditions

Custom exceptions

Implement Exception for business errors. Override toString for friendly messages and carry structured fields.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class ValidationError implements Exception {
ValidationError(this.field, this.message);
final String field;
final String message;
@override
String toString() => '字段 $field 无效: $message';
}
void main() {
try {
throw ValidationError('email', '格式错误');
} on ValidationError catch (e) {
print(e.field); // email
print(e);
}
}
// implements Exception marks the class as an exception
// can carry structured fields
// `on` clause matches custom types precisely
// lets callers dispatch by type

Async errors

Exceptions thrown after await are catchable with try/catch. Unawaited Future errors are silently lost.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Future<void> load() async {
throw Exception('加载失败');
}
Future<void> main() async {
try {
await load();
} catch (e) {
print('异步捕获: $e');
}
}
// errors from unawaited futures are hard to catch
// use catchError for chained handling:
// load().catchError(print);
// unhandled errors may enter the zone
// recommend a unified error-handling strategy

Stream errors

Pass an onError callback when listening to a Stream. StreamController.addError injects an error into the stream.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import 'dart:async';
void main() {
final controller = StreamController<int>();
controller.stream.listen(
(data) => print('数据: $data'),
onError: (e) => print('流错误: $e'),
onDone: () => print('流结束'),
);
controller.add(1);
controller.addError('意外错误');
controller.close();
}
// addError injects an error into the stream
// onError callback handles stream errors
// onDone fires after the stream closes
// stream errors do not interrupt other data

Assertions

assert checks a condition in development; it throws AssertionError on failure. Stripped from release builds.

1
2
3
4
5
6
7
8
9
void main() {
final score = 85;
assert(score >= 0, '分数不能为负'); // passes
print('断言通过');
}
// false condition throws AssertionError
// second argument is the failure message
// release-mode `dart compile` strips asserts
// suited for invariants, not for user input

12.Files & I/O

File I/O, JSON, and standard input and output.

Read files

The File class from dart:io reads/writes files. readAsString loads an entire text file — import 'dart:io'.

1
2
3
4
5
6
7
8
9
10
11
import 'dart:io';
Future<void> main() async {
final text = await File('data.txt').readAsString();
print(text);
final exists = await File('data.txt').exists();
print(exists);
}
// readAsString reads the whole file into memory
// for large files use streaming reads
// missing path throws FileSystemException
// exists returns a bool indicating presence

Write files

writeAsString writes text; writeAsBytes writes binary. Pass mode: FileMode.append to append.

1
2
3
4
5
6
7
8
9
10
11
12
import 'dart:io';
Future<void> main() async {
await File('out.txt').writeAsString('Hello\n');
await File('out.txt').writeAsString('World\n',
mode: FileMode.append); // append
final content = await File('out.txt').readAsString();
print(content);
}
// default mode overwrites the existing file
// FileMode.append appends to the end
// FileMode.writeOnly opens write-only and overwrites
// writing Chinese text defaults to UTF-8 encoding

Read line by line

readAsLines splits a file into a list of lines. For large files, use openRead with LineSplitter to stream.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import 'dart:io';
import 'dart:convert';
Future<void> main() async {
final lines = await File('log.txt').readAsLines();
for (final line in lines) {
print(line);
}
final stream = File('big.log').openRead()
.transform(utf8.decoder)
.transform(const LineSplitter());
await for (final line in stream) {
print(line);
}
}
// readAsLines is good for small files
// for large files use openRead streaming
// LineSplitter splits by lines
// streaming avoids exhausting memory

JSON processing

dart:convert provides jsonEncode / jsonDecode for serialization. JSON numbers become int or double.

1
2
3
4
5
6
7
8
9
10
11
12
import 'dart:convert';
void main() {
final data = {'name': 'Rex', 'age': 5};
final encoded = jsonEncode(data);
print(encoded);
final decoded = jsonDecode(encoded) as Map<String, dynamic>;
print(decoded['name']);
}
// jsonDecode returns dynamic, cast as needed
// numbers parse as int or double
// unknown fields remain in the Map
// complex objects need manual mapping

Standard input/output

stdin.readLineSync reads a line synchronously; stdout.writeln writes a line. Ideal for CLI programs.

1
2
3
4
5
6
7
8
9
10
import 'dart:io';
void main() {
stdout.write('请输入姓名: ');
final name = stdin.readLineSync() ?? '';
stdout.writeln('你好,$name');
}
// readLineSync reads a line, returns String?
// at EOF returns null, supply a default
// stdout.write does not append a newline
// stderr.writeln writes to the error stream

Directory operations

Directory.list enumerates entries; create makes a directory; delete removes one. Returned entities are FileSystemEntity.

1
2
3
4
5
6
7
8
9
10
11
12
import 'dart:io';
Future<void> main() async {
await Directory('data/sub').create(recursive: true);
await for (final entity in Directory('.').list()) {
print('${entity.path} ${entity is Directory ? '目录' : '文件'}');
}
await Directory('data').delete(recursive: true);
}
// create recursive: creates nested directories
// list iterates directory contents lazily
// delete recursive: removes recursively
// entity handles files and directories uniformly

Binary bytes

readAsBytes reads binary; Uint8List models bytes; writeAsBytes writes them. Suitable for images, audio, etc.

1
2
3
4
5
6
7
8
9
10
11
12
13
import 'dart:io';
import 'dart:typed_data';
Future<void> main() async {
final bytes = await File('image.png').readAsBytes();
print('大小: ${bytes.length} 字节');
final copy = Uint8List.fromList(bytes);
await File('copy.png').writeAsBytes(copy);
print('已复制');
}
// readAsBytes returns Uint8List
// bytes are suited for non-text data
// fromList copies a byte sequence
// for large files use openWrite for streaming writes

Path operations

File / Directory expose the full .path. .absolute resolves the absolute path; .uri gives a file:// URI.

1
2
3
4
5
6
7
8
9
10
11
12
import 'dart:io';
void main() {
final file = File('a/b/data.txt');
print(file.path); // full path
print(file.absolute.path); // absolute path
print(file.uri); // file:// URI
print(Platform.pathSeparator);
}
// `path` returns the full path string
// `absolute` resolves to an absolute path
// `uri` converts to a file:// URI
// Windows and Unix use different separators

13.Common Pitfalls

The pitfalls you are most likely to hit in everyday Dart development, and how to write it correctly.

Misusing null-assertion

Bypassing null checks with ! often crashes at runtime. Prefer null-checks, ??, and type promotion.

1
2
3
4
5
6
7
8
9
10
11
// BAD: using `!` masks nullable design
String? name = 'Rex';
final len = name!.length; // depends on a runtime assert
print(len);
// GOOD: leverage type promotion
String? nick = '小雷';
if (nick != null) {
print(nick.length);
}
// GOOD: `??` provides a default value
print(name?.toUpperCase() ?? '未知');

Collection == comparison

List / Map == compares by reference, not contents. Use a helper for structural equality.

1
2
3
4
5
6
7
8
9
// BAD: `==` compares references
final a = [1, 2];
final b = [1, 2];
print(a == b); // false
// GOOD: compare contents, not references
print(a.join(',') == b.join(',')); // true
// GOOD: structural comparison recommended
// listEquals(a, b) from package:collection
// strings and numbers compare contents with `==` directly

const vs final

const requires a compile-time value; final accepts a runtime value. Writing const for runtime data fails compilation.

1
2
3
4
5
6
7
8
9
// BAD: runtime values cannot be const
// const now = DateTime.now(); // compile error
// GOOD: use `final` for runtime values
final now = DateTime.now();
// GOOD: use `const` for compile-time constants
const pi = 3.14159;
// const values are canonicalized and shared
// final only prevents reassignment
// collection elements can also be const

Lazy Iterable

map / where return lazy Iterables that recompute on iteration. Snapshot with toList when needed.

1
2
3
4
5
6
7
8
9
10
11
// BAD: a lazy Iterable recomputes on each iteration
final nums = [1, 2, 3];
final lazy = nums.where((n) => n.isOdd);
nums.add(5);
print(lazy.toList()); // [1, 3, 5] includes the new element
// GOOD: use `toList()` for a snapshot
final snapshot = nums.where((n) => n.isOdd).toList();
nums.add(7);
print(snapshot); // still [1, 3, 5]
// underlying changes do not affect materialized results
// a Stream can only be listened to once

Forgetting await

Calling an async function without await gives you a Future, not its result. Use Future.wait for parallelism.

1
2
3
4
5
6
7
8
9
10
11
12
// BAD: forgetting `await` yields a Future
Future<String> fetch() async => '数据';
Future<void> main() async {
final result = fetch(); // a Future, not a String
print(result);
// GOOD: only the awaited result is the value
final text = await fetch();
print(text);
// await multiple concurrently:
final all = await Future.wait([fetch(), fetch()]);
print(all);
}

Cascade returns receiver

The cascade .. returns the receiver, not the last expression's value. Don't cascade when you need the result.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class User {
String name = '';
int age = 0;
}
void main() {
// BAD: cascade returns the receiver
final result = [1, 2]..add(3);
print(result); // [1, 2, 3], not 3
// GOOD: cascade mutates in place and returns the same object
final user = User()
..name = 'Rex'
..age = 5;
print(user.name);
// use a regular call when you need the function's return value
print('abc'.toUpperCase());
}

Modifying during iteration

Adding or removing during iteration throws ConcurrentModificationError. Use removeWhere or collect first.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// BAD: mutating while iterating throws
final items = [1, 2, 3, 4];
// for (final n in items) { items.remove(n); }
// throws ConcurrentModificationError
// GOOD: `removeWhere` filters safely
final nums = [1, 2, 3, 4];
nums.removeWhere((n) => n.isEven);
print(nums); // [1, 3]
// GOOD: collect first, then process
final keep = <int>[];
for (final n in nums) {
if (n.isOdd) keep.add(n);
}
print(keep);

Strings by code units

length counts UTF-16 code units — Chinese and emoji take more than one. Iterate runes for real characters.

1
2
3
4
5
6
7
8
9
10
11
12
// BAD: counting chars with `length`
final s = '你好';
print(s.length); // 2 code units
// emoji case:
final emoji = '👍';
print(emoji.length); // 2! for a single glyph
// GOOD: count code points with `runes`
print(emoji.runes.length); // 1
// GOOD: iterate `runes` for per-glyph handling
for (final r in emoji.runes) {
print(String.fromCharCode(r));
}

14.Concurrency & Async

Isolates, Future, Stream, and the event loop.

Isolate concurrency

An isolate is Dart's concurrency unit — its own memory and event loop. Start with Isolate.run or Isolate.spawn.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import 'dart:isolate';
Future<void> main() async {
final result = await Isolate.run(() {
var sum = 0;
for (var i = 0; i < 100000; i++) {
sum += i;
}
return sum;
});
print('结果: $result');
}
// Isolate.run returns Future<result>
// isolated memory, no shared mutable state
// suited for CPU-bound work
// disposed automatically when finished

spawn & ports

Isolate.spawn starts an isolate with an entry function. SendPort sends, ReceivePort receives.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import 'dart:isolate';
void entry(SendPort sendPort) {
sendPort.send('来自新 isolate');
}
Future<void> main() async {
final receive = ReceivePort();
await Isolate.spawn(entry, receive.sendPort);
final msg = await receive.first;
print(msg);
receive.close();
}
// `spawn` entry must be a top-level function or static method
// messages are passed through ports
// `receive.first` awaits the first message
// remember to close the port when done

Future basics

Future represents a result that's available later. Future.value resolves immediately; await waits.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Future<int> addLater(int a, int b) async {
await Future.delayed(Duration(milliseconds: 10));
return a + b;
}
Future<void> main() async {
final future = Future.value(42);
print(await future);
final result = await addLater(1, 2);
print(result);
}
// `async` function returns a Future
// `await` suspends until the Future completes
// Future.delayed schedules a delay
// Future.value wraps an immediate result

async / await

async marks an async function; await pauses for the Future. await is only valid inside async.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Future<String> fetchUser() async {
await Future.delayed(Duration(milliseconds: 50));
return 'Rex';
}
Future<void> main() async {
print('开始加载');
final name = await fetchUser();
print('加载到: $name');
final results = await Future.wait([
fetchUser(),
fetchUser(),
]);
print(results);
}
// `await` pauses the current flow but does not block the main thread
// multiple awaits run sequentially by default
// Future.wait awaits multiple futures concurrently
// errors propagate to the `await` site and can be caught

Stream

A Stream is a sequence of async events. Use listen to subscribe, await for to iterate. Different from Future's single value.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import 'dart:async';
Stream<int> tick(int max) async* {
for (var i = 0; i < max; i++) {
await Future.delayed(Duration(milliseconds: 10));
yield i;
}
}
Future<void> main() async {
await for (final n in tick(3)) {
print('tick $n');
}
tick(2).listen((n) => print('listen $n'));
await Future.delayed(Duration(milliseconds: 100));
}
// `await for` consumes stream events sequentially
// `listen` registers a listener callback
// a stream emits once and cannot be replayed
// events arrive one at a time

StreamController

StreamController drives a stream manually — add pushes data, addError pushes errors, close ends it. broadcast enables multi-listeners.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import 'dart:async';
void main() {
final controller = StreamController<int>();
controller.stream.listen((n) => print('收到 $n'));
controller.add(1);
controller.add(2);
controller.close();
final broadcast = StreamController<int>.broadcast();
broadcast.stream.listen((n) => print('A $n'));
broadcast.stream.listen((n) => print('B $n'));
broadcast.add(5);
broadcast.close();
}
// by default the stream allows only one listener
// broadcast allows multiple listeners
// `add` pushes data into the stream
// remember to close the controller to free resources

Waiting for multiple Futures

Future.wait waits for all to complete; Future.any resolves with the first to finish.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Future<int> slow() async {
await Future.delayed(Duration(milliseconds: 30));
return 1;
}
Future<int> fast() async {
await Future.delayed(Duration(milliseconds: 10));
return 2;
}
Future<void> main() async {
final all = await Future.wait([slow(), fast()]);
print(all); // [1, 2]
final first = await Future.any([slow(), fast()]);
print(first); // 2
}
// `wait` returns the list when all succeed
// any failure propagates by default
// `any` returns the first to complete
// `eagerError` controls immediate failure

Completer

Completer gives you manual control over a Future's completion — handy for wrapping callback APIs in async/await.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import 'dart:async';
Future<String> fromCallback() {
final completer = Completer<String>();
Future.delayed(Duration(milliseconds: 20), () {
completer.complete('回调结果');
});
return completer.future;
}
Future<void> main() async {
final result = await fromCallback();
print(result);
}
// `complete` makes the Future succeed
// `completeError` makes the Future fail
// can only call complete once
// used to wrap third-party callbacks

15.Networking

HTTP requests, WebSocket, and TCP sockets.

HTTP client

Use dart:io's HttpClient for HTTP. getUrl returns a request; close it and read the body.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import 'dart:io';
import 'dart:convert';
Future<void> main() async {
final client = HttpClient();
final request = await client.getUrl(Uri.parse('https://example.com'));
final response = await request.close();
final body = await response.transform(utf8.decoder).join();
print('状态码: ${response.statusCode}');
print(body.substring(0, 50));
client.close();
}
// HttpClient is low-level and must be closed manually
// response body is a byte stream, decode with `utf8.decoder`
// statusCode 200 means success
// for simple scenarios prefer `package:http`

package:http

package:http wraps common requests. http.get / http.post return a Response whose body is a string.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<void> main() async {
final response = await http.get(
Uri.parse('https://example.com'),
);
print('状态码: ${response.statusCode}');
print(response.body.substring(0, 50));
final post = await http.post(
Uri.parse('https://example.com/api'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'name': 'Rex'}),
);
print(post.statusCode);
}
// add `http` dependency to pubspec.yaml
// `Response.body` is already decoded as String
// `headers` passes request headers
// check `statusCode` for success

URI parsing

Uri.parse parses a URL; queryParameters reads the query string; Uri.http builds a request URL.

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
final uri = Uri.parse('https://api.com/users?id=5&page=2');
print(uri.scheme); // https
print(uri.host); // api.com
print(uri.path); // /users
print(uri.queryParameters); // {id: 5, page: 2}
final built = Uri.http('api.com', '/users', {'id': '5'});
print(built);
}
// `queryParameters` are auto-decoded
// `Uri.https` uses TLS
// `queryParametersAll` handles duplicate keys
// `replace` can derive a new URI

JSON API

Call a JSON API and parse the response with jsonDecode. Combine package:http with Future.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<void> main() async {
final response = await http.get(
Uri.parse('https://api.example.com/users'),
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body) as List;
for (final item in data) {
final user = item as Map<String, dynamic>;
print(user['name']);
}
}
}
// check the status code before parsing
// JSON arrays become List, objects become Map
// missing field returns null
// in real projects wrap with model classes

WebSocket

WebSocket is full-duplex. WebSocket.connect establishes the link; add sends; iterate to receive.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import 'dart:io';
Future<void> main() async {
final ws = await WebSocket.connect('wss://example.com/ws');
ws.add('你好');
await for (final message in ws) {
print('收到: $message');
if (message == 'bye') {
await ws.close();
}
}
}
// `connect` establishes the connection
// `add` sends a message
// receive messages via the stream
// `close` ends the connection gracefully

TCP socket

Socket.connect establishes a TCP connection. Write the request, stream the response. Good for low-level protocols.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import 'dart:io';
Future<void> main() async {
final socket = await Socket.connect('example.com', 80);
socket.write('GET / HTTP/1.0\r\n\r\n');
await for (final data in socket) {
final text = String.fromCharCodes(data);
if (text.contains('HTTP')) print(text);
}
socket.destroy();
}
// Socket provides a byte stream
// `write` sends the request content
// read response chunks as a stream
// `destroy` closes immediately

HTTP server

HttpServer.bind starts a local server, then await for iterates over requests. Great for dev tools.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import 'dart:io';
Future<void> main() async {
final server = await HttpServer.bind('localhost', 8080);
print('监听 8080 端口');
await for (final request in server) {
request.response.headers.set('Content-Type', 'text/plain; charset=utf-8');
request.response.write('你好,路径 ${request.uri.path}');
await request.response.close();
}
}
// `bind` binds address and port
// each request arrives through `await for`
// the `response` object writes the reply
// `close` ends this response

Timeouts & errors

Network calls may time out or fail. Use .timeout and catch each error type explicitly.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import 'dart:async';
import 'package:http/http.dart' as http;
Future<void> main() async {
try {
final response = await http
.get(Uri.parse('https://example.com'))
.timeout(const Duration(seconds: 5));
print(response.statusCode);
} on TimeoutException {
print('请求超时');
} catch (e) {
print('网络错误: $e');
}
}
// `timeout` throws TimeoutException after the deadline
// network errors are usually SocketException
// handle each error type separately
// in production add exponential-backoff retries

16.Time & Date

DateTime, Duration, formatting, and timestamps.

Current time

DateTime.now() returns the current local time. All fields are accessible, including milliseconds.

1
2
3
4
5
6
7
8
9
10
11
void main() {
final now = DateTime.now();
print('${now.year}-${now.month}-${now.day}');
print('${now.hour}:${now.minute}:${now.second}');
print(now.weekday); // Mon=1 Sun=7
print(now.millisecondsSinceEpoch);
}
// `now` returns local-time
// month/day start from 1
// weekday 1-7, with Monday = 1
// every field is `int`

Constructing time

DateTime(year, month, day, ...) constructs a time; DateTime.utc constructs UTC time.

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
final birthday = DateTime(2020, 1, 15);
print(birthday);
final launch = DateTime(2026, 8, 2, 10, 30);
print(launch);
final utc = DateTime.utc(2026, 1, 1);
print(utc.isUtc); // true
}
// omitted parts default to 0 or 1
// invalid month/day values carry over automatically
// construct UTC with `DateTime.utc`
// compare with `isBefore`/`isAfter`

Duration

Duration represents a span of time — hours, minutes, seconds, microseconds. Supports arithmetic, comparison, and unit conversion.

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
final halfHour = Duration(minutes: 30);
final fiveMin = Duration(minutes: 5);
print(halfHour.inSeconds); // 1800
print(halfHour + fiveMin); // 35 minutes
print(halfHour > fiveMin); // true
final d = Duration(days: 1, hours: 2);
print('${d.inHours} 小时'); // 26 hours
}
// `inSeconds`/`inMinutes`/etc. convert units
// supports `+ - * /` arithmetic
// supports comparison
// commonly use `Duration(milliseconds: n)` for delays

Formatting output

Dart has no built-in strftime — use padLeft to zero-pad, or the intl package's DateFormat.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void main() {
final now = DateTime.now();
final y = now.year.toString();
final m = now.month.toString().padLeft(2, '0');
final d = now.day.toString().padLeft(2, '0');
print('$y-$m-$d');
print(now.toIso8601String());
// `intl` package provides date formatting:
// import 'package:intl/intl.dart';
// print(DateFormat('yyyy-MM-dd').format(now));
}
// `padLeft` pads to the given width
// `toIso8601String` outputs ISO format
// `intl` supports localized date patterns
// for simple cases manual concatenation is enough

Parsing time

DateTime.parse reads an ISO-8601 string; toIso8601String produces one. Both ease data exchange.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void main() {
final parsed = DateTime.parse('2026-08-02 10:30:00');
print(parsed.year); // 2026
final iso = DateTime.parse('2026-08-02T10:30:00Z');
print(iso.isUtc); // true
final parts = '2026-08-02'.split('-');
final d = DateTime(
int.parse(parts[0]), int.parse(parts[1]), int.parse(parts[2]));
print(d.day);
}
// supports several ISO formats
// no timezone defaults to local time
// trailing `Z` means UTC
// invalid format throws FormatException

Time arithmetic

add / subtract shift time by a Duration; difference returns the Duration between two times.

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
final start = DateTime(2026, 8, 2, 10);
final later = start.add(Duration(hours: 5));
print(later.hour); // 15
final earlier = start.subtract(const Duration(days: 1));
print(earlier.day); // 1
final diff = later.difference(start);
print(diff.inHours); // 5
}
// `add`/`subtract` return a new instance
// the original instance is immutable
// `difference` is always non-negative
// for direction-aware comparison use `isBefore`

Timestamps

Millisecond/microsecond timestamps store and order easily. fromMillisecondsSinceEpoch rebuilds a DateTime.

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
final now = DateTime.now();
final ms = now.millisecondsSinceEpoch;
print(ms);
final back = DateTime.fromMillisecondsSinceEpoch(ms);
print(back.toIso8601String());
final day = DateTime(now.year, now.month, now.day);
print(day.millisecondsSinceEpoch);
}
// timestamp is milliseconds since 1970-01-01
// store as UTC to avoid timezone confusion
// `fromMicrosecondsSinceEpoch` works with microseconds
// the same timestamp yields equal reconstructed DateTimes

Timezone

DateTime defaults to local time. Use isUtc to test; toUtc / toLocal to convert. Persist in UTC.

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
final now = DateTime.now();
print(now.isUtc); // false
final utc = now.toUtc();
print(utc.isUtc); // true
final local = utc.toLocal();
print(local == now); // same instant
}
// `toUtc`/`toLocal` convert without changing the instant
// DateTime does not carry an IANA timezone name
// for IANA timezones use `package:timezone`
// persist in UTC recommended

Stopwatch

Stopwatch measures elapsed time. start / stop / reset control it; elapsed returns a Duration.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void main() {
final watch = Stopwatch()..start();
var sum = 0;
for (var i = 0; i < 1000000; i++) {
sum += i;
}
watch.stop();
print('耗时 ${watch.elapsedMilliseconds} ms');
print('微秒 ${watch.elapsedMicroseconds}');
}
// `start` begins the timer
// `elapsed` returns a Duration
// `elapsedMilliseconds` gives milliseconds
// `reset` zeroes the watch and restarts

17.Processes & Environment

Child processes, environment variables, standard streams, and signals.

Run child process

Process.run executes a command and waits. result.stdout / stderr capture the output.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import 'dart:io';
Future<void> main() async {
final result = await Process.run('dart', ['--version']);
print('退出码: ${result.exitCode}');
print('输出: ${result.stdout}');
final ls = await Process.run(
Platform.isWindows ? 'cmd' : 'ls',
Platform.isWindows ? ['/c', 'dir'] : ['-la'],
);
print(ls.stdout);
}
// `run` waits for the process to exit and returns its result
// `stdout`/`stderr` are String or List<int>
// exitCode 0 means success
// for interactivity use `Process.start`

Streaming process

Process.start returns immediately — stream stdout and write stdin interactively. For long-running tasks.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import 'dart:io';
import 'dart:convert';
Future<void> main() async {
final process = await Process.start('dart', ['--version']);
process.stdout
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen((line) => print('子进程输出: ${line.trim()}'));
final exitCode = await process.exitCode;
print('退出码: $exitCode');
}
// `start` does not wait for the process to exit
// `stdout` is a stream, listen in real time
// for interactivity write to `stdin`
// use `environment` to customize variables

Environment variables

Platform.environment is a read-only Map of env vars. Missing keys return null.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import 'dart:io';
void main() {
final env = Platform.environment;
final path = env['PATH'] ?? '未设置';
print('PATH: $path');
print('有 HOME: ${env.containsKey('HOME')}');
for (final entry in env.entries) {
if (entry.key.startsWith('FLUTTER')) {
print('${entry.key}=${entry.value}');
}
}
}
// `environment` is a read-only Map
// missing keys return null
// `containsKey` checks for presence
// pass `environment` when spawning to inject variables

Exit code

Set the process exit code via exitCode. 0 means success; non-zero signals failure categories to shells.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import 'dart:io';
void main(List<String> args) {
if (args.isEmpty) {
stderr.writeln('用法: app <参数>');
exitCode = 1;
return;
}
print('处理 ${args[0]}');
exitCode = 0; // success
}
// exitCode 0 means success
// non-zero indicates an error type
// `exit()` terminates the process immediately
// scripts rely on exit code to judge results

Args & script

args is the command-line argument list; Platform.script is the entry path; Directory.current is the cwd.

1
2
3
4
5
6
7
8
9
10
11
12
13
import 'dart:io';
void main(List<String> args) {
print('参数个数: ${args.length}');
for (var i = 0; i < args.length; i++) {
print('参数 $i: ${args[i]}');
}
print('脚本路径: ${Platform.script}');
print('当前目录: ${Directory.current.path}');
}
// `args` excludes the program name
// `Platform.script` is the entry script path
// `Directory.current` is the current working directory
// for complex args use the `args` package

Standard streams

stdout / stderr for output, stdin for input. flush forces a buffer write; stderr skips piped output.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import 'dart:io';
void main() {
stdout.write('请输入:');
stdout.flush();
final input = stdin.readLineSync() ?? '';
stdout.writeln('你输入了: $input');
if (input.isEmpty) {
stderr.writeln('警告:输入为空');
}
}
// `stdout.write` does not append a newline
// `flush` sends buffered output immediately
// `stderr` writes to the error stream
// when piped, stdout is redirected

Filesystem

File / Directory cover the filesystem — create, delete, rename, exists.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import 'dart:io';
Future<void> main() async {
final dir = Directory('data/sub');
await dir.create(recursive: true);
final file = File('data/note.txt');
await file.writeAsString('内容');
print(await file.exists()); // true
print(file.lengthSync()); // bytes
await file.rename('data/new.txt');
await Directory('data').delete(recursive: true);
}
// `create(recursive:)` creates nested directories
// `exists` checks for presence
// `lengthSync` reads size synchronously
// `rename` moves or renames a file

Signal handling

ProcessSignal responds to system signals (SIGINT / SIGTERM) — for graceful shutdown and cleanup.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import 'dart:io';
Future<void> main() async {
ProcessSignal.sigint.watch().listen((_) {
print('收到中断,清理退出');
exit(0);
});
print('运行中,按 Ctrl+C 退出');
await Future.delayed(Duration(seconds: 30));
}
// sigint is Ctrl+C
// sigterm is the default kill signal
// `watch` returns a stream of the signal
// call `exit` after handling
// on Windows only some signals are available

18.Regular Expressions

Matching, replacing, splitting, and grouping with RegExp.

Create & match

RegExp models a regular expression. hasMatch tests existence. The r prefix keeps the string raw.

1
2
3
4
5
6
7
8
9
10
11
void main() {
final digits = RegExp(r'\d+');
print(digits.hasMatch('abc123')); // true
print(digits.hasMatch('abc')); // false
final email = RegExp(r'^[\w.-]+@[\w-]+\.\w+$');
print(email.hasMatch('[email protected]'));
}
// `r` prefix marks a raw string
// \d digit, \w word char, \s whitespace
// ^ start, $ end of line
// `hasMatch` only checks existence

First match

firstMatch returns the first match. group extracts a capture; start / end give positions. null when there's no match.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void main() {
final re = RegExp(r'\d+');
final match = re.firstMatch('编号 42,编号 99');
if (match != null) {
print(match.group(0)); // 42
print(match.start); // match start
print(match.end); // match end
print(match.matched); // 42
}
}
// `firstMatch` returns the first match
// returns null when there is no match
// `group(0)` is the whole match
// `group(n)` is the nth capture group

All matches

allMatches returns an iterable of matches. Combine with patterns to extract all hits.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void main() {
final re = RegExp(r'\d+');
final matches = re.allMatches('a1 b22 c333');
final numbers = <String>[];
for (final m in matches) {
numbers.add(m.group(0)!);
}
print(numbers); // [1, 22, 333]
final list = RegExp(r'\d+')
.allMatches('a1 b22')
.map((m) => m.group(0)!)
.toList();
print(list);
}
// iterate each match object
// `group(0)` retrieves the whole match
// `map` chains extraction more concisely
// lazy iteration computes on demand

Replacement

replaceAll swaps every match; replaceFirst swaps just the first. Pass a callback for dynamic replacements.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void main() {
final re = RegExp(r'\d+');
print(re.replaceAll('a1b2', '#')); // a#b#
print(re.replaceFirst('a1b2', '#')); // a#b2
final result = re.replaceAllMapped('a1b2', (m) {
final n = int.parse(m.group(0)!);
return '${n + 1}';
});
print(result); // a2b3
}
// `replaceAll` replaces all occurrences
// `replaceFirst` replaces only the first
// callback form lets you compute per match
// the original is unchanged; returns a new string

Split

String.split accepts a RegExp — split on a pattern. The separator itself is dropped.

1
2
3
4
5
6
7
8
9
10
11
void main() {
final text = 'a, b, c';
final parts = text.split(RegExp(r',\s*'));
print(parts); // [a, b, c]
print('one two three'.split(RegExp(r'\s+')));
print(text.replaceAll(RegExp(r',\s*'), ' | '));
}
// `split` cuts at every match
// separators are not in the result
// for complex separators use regex
// for simple separators pass a string

Capture groups

Parentheses define capture groups. group(n) gets the nth; (?<name>...) names one for namedGroup.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void main() {
final re = RegExp(r'(\d{4})-(\d{2})-(\d{2})');
final m = re.firstMatch('日期 2026-08-02');
if (m != null) {
print(m.group(1)); // 2026
print(m.group(2)); // 08
print(m.group(3)); // 02
}
final named = RegExp(r'(?<year>\d{4})');
final nm = named.firstMatch('2026');
print(nm?.namedGroup('year'));
}
// `group(1)+` are capture groups
// non-capturing group `(?:...)` does not consume a number
// named groups improve readability
// `namedGroup` retrieves by name

Flags

Pass flags to the RegExp constructor — caseSensitive, multiLine, dotAll, unicode.

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
final re = RegExp('dart', caseSensitive: false);
print(re.hasMatch('Hello DART')); // true
final multiline = RegExp('^a', multiLine: true);
print(multiline.hasMatch('x\na\nb')); // true
final dotAll = RegExp('a.b', dotAll: true);
print(dotAll.hasMatch('a\nb')); // true
}
// `caseSensitive: false` ignores letter case
// `multiLine` lets `^` match start of every line
// `dotAll` lets `.` match newlines
// `unicode: true` enables Unicode mode

Common patterns

Store common validators — email, phone, URL, IP — as reusable constants.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void main() {
final email = RegExp(r'^[\w.+-]+@[\w-]+\.\w+$');
final phone = RegExp(r'^1[3-9]\d{9}$');
final url = RegExp(r'^https?://\S+$');
final ip = RegExp(r'^\d{1,3}(\.\d{1,3}){3}$');
print(email.hasMatch('[email protected]'));
print(phone.hasMatch('13800138000'));
print(url.hasMatch('https://dart.dev'));
print(ip.hasMatch('192.168.1.1'));
}
// email: general validation
// phone: 11-digit China mobile
// URL: http/https
// IP: four dotted-decimal octets

19.Build & Debug

pub, static analysis, formatting, and testing.

pubspec configuration

pubspec.yaml is the package manifest — name, version, SDK constraint, dependencies. Run dart pub get to resolve.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// pubspec.yaml example:
// name: myapp
// description: 示例应用
// version: 1.0.0
// environment:
// sdk: '>=3.0.0 <4.0.0'
// dependencies:
// http: ^1.1.0
// dev_dependencies:
// test: ^1.24.0
// version constraint:
// ^1.1.0 means >=1.1.0 <2.0.0
// install dependencies:
// dart pub get

pub commands

Manage dependencies with pub: get, upgrade, outdated, add, remove.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// install dependencies:
// dart pub get
// upgrade all dependencies:
// dart pub upgrade
// list outdated dependencies:
// dart pub outdated
// add a dependency:
// dart pub add http
// remove a dependency:
// dart pub remove http
// search packages:
// dart pub search json
// publish:
// dart pub publish
// versions are locked in pubspec.lock

Static analysis

dart analyze does static analysis — run in CI to enforce quality.

1
2
3
4
5
6
7
8
9
10
11
12
// analyze the current package:
// dart analyze
// analyze a specific file:
// dart analyze lib/main.dart
// common checks:
// unused imports
// type errors
// discouraged patterns
// apply lint fixes:
// dart fix --apply
// CI integration:
// dart analyze && dart test

Formatting

dart format enforces a consistent style — indentation, quotes, line breaks. Use in team workflows.

1
2
3
4
5
6
7
8
9
10
11
// format current directory:
// dart format .
// format a specific file:
// dart format lib/main.dart
// check without modifying:
// dart format --output=none --set-exit-if-changed .
// style conventions:
// 2-space indent
// prefer single quotes
// no trailing whitespace
// CI checks format consistency

Unit tests

package:test provides test() and expect. Run with dart test. Place tests in test/.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// test/calculator_test.dart:
import 'package:test/test.dart';
int add(int a, int b) => a + b;
void main() {
test('addition is correct', () {
expect(add(1, 2), 3);
expect(add(-1, 1), 0);
});
group('edge cases', () {
test('large numbers add', () {
expect(add(1000000, 1), 1000001);
});
});
}
// run tests:
// dart test
// run a single file:
// dart test test/calculator_test.dart
// filter by name:
// dart test -n addition

Compile & deploy

dart compile produces executables: exe (native), js (web), aot-snapshot, kernel.

1
2
3
4
5
6
7
8
9
10
11
12
// compile a native executable:
// dart compile exe bin/main.dart
// specify the output:
// dart compile exe bin/main.dart -o myapp
// compile to JS (browser):
// dart compile js lib/main.dart
// AOT snapshot:
// dart compile aot-snapshot bin/main.dart
// compile kernel:
// dart compile kernel bin/main.dart
// after compile no Dart SDK is needed at runtime
// native exe starts fast

lint rules

Configure lints in analysis_options.yaml. The lints package ships a recommended set.

1
2
3
4
5
6
7
8
9
10
11
12
13
// analysis_options.yaml example:
// include: package:lints/recommended.yaml
// linter:
// rules:
// - prefer_final_locals
// - avoid_print
// analyzer:
// language:
// strict-casts: true
// recommended ruleset:
// package:lints/recommended.yaml
// Flutter projects use flutter_lints
// `dart analyze` applies these rules

Debugging

print is the simplest debugger. assert checks invariants; IDE breakpoints step through code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void main() {
final total = calculate(3, 4);
print('调试: total=$total');
assert(total > 0, '结果应为正');
print(total);
}
int calculate(int a, int b) {
final sum = a + b;
// `dart:developer` `log` supports levels:
// log('中间值 $sum', name: 'calc');
return sum;
}
// `print` outputs to the console
// `dart:developer` `log` supports levels
// asserts are stripped in release mode
// breakpoints support step-through debugging
// `debugPrint` is for Flutter's high-volume logging

Official Links

Direct links to the official docs and resources.

About this Cheatsheet

This is a self-contained cheatsheet for Dart 3, covering the language core and the most-used standard libraries for ~80% of real-world code. It leans toward modern idioms — sound null safety, records and patterns, switch expressions, sealed class, cascade operator, and isolate-based concurrency. Dart is designed by Google and powers Flutter; it ships with both JIT (fast iteration) and AOT (efficient release) execution. For the authoritative reference, see the official Dart language tour and Effective Dart. The 19 chapters each focus on one topic — from your first program through isolates, common pitfalls, build & test. Every chapter is split into 8 short, runnable examples (5–15 lines each), totalling about 152 topics. Snippets are intentionally short and self-explanatory; comments are kept in Chinese to support language learners. All processing happens in your browser — nothing is uploaded or tracked. This page is part of GuruToolkit's free developer toolkit; snippets are free to use with no warranty.

Version 2.1.0