Open-source libraries used

1 libraries are bundled into this tool's code.

Dart Cheat Sheet

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.

Version 2.1.1