Null Safety
Non-nullable by default
Section titled “Non-nullable by default”Dart has sound null safety: a type cannot hold null unless you explicitly say so with ?. This is a hard guarantee, not a lint — if the type system says a value is non-null, it genuinely cannot be null at runtime.
int a = 1; // non-nullable — can never be nullint? b = null; // nullable — the ? opts in to null
a = null; // ❌ compile error: a cannot be nullb = 42; // ✅ a nullable can also hold a real value“Sound” is the key word: because the guarantee is airtight, the compiler can optimize on it and you can trust it. A String name widget property will never surprise you with a null.
The four operators you’ll use daily
Section titled “The four operators you’ll use daily”String? maybeName;
// ?. — null-aware access: call only if not null, else the whole thing is nullfinal len = maybeName?.length; // int? — null if maybeName is null
// ?? — if-null: provide a fallback when the left side is nullfinal safe = maybeName ?? 'Guest'; // String — never null
// ??= — assign only if currently nullmaybeName ??= 'Default';
// ! — null assertion: "I promise this isn't null" (throws if you're wrong)final definite = maybeName!; // String — DANGER if it's actually nullThe ! operator is the one to respect: it silences the compiler by asserting non-null, and if you’re wrong it throws at runtime. Every ! is a small bet — use it only when you can prove the value is non-null.
Flow analysis narrows for you
Section titled “Flow analysis narrows for you”You rarely need !, because Dart’s flow analysis narrows a nullable to non-null after a check:
void greet(String? name) { if (name == null) return; // From here down, Dart KNOWS name is String (not String?) — no ! needed. print(name.toUpperCase());}flowchart TB
start["name: String?"] --> check{"name == null?"}
check -->|yes| ret["return early"]
check -->|no| promoted["name: String
(promoted, safe to use)"] There’s a catch that bites Flutter devs: flow analysis works on local variables, not on instance fields or getters (they could change between the check and the use). For a nullable field, copy it to a local first, then check the local.
late: a promise to initialize before use
Section titled “late: a promise to initialize before use”late says “this will be non-null by the time anyone reads it, just not at declaration.” Useful for values set in initState or computed once.
late final String config; // no value yet, but promised before first read// If you read it before assigning, you get a LateInitializationError.Use late sparingly — it trades a compile-time guarantee for a runtime one. A misused late is just a ! in disguise.