Null Safety

4.1 Non-Nullable by Default

Dart 3 enforces 100% sound null safety: if the type system says a value is non-nullable, it can never be null at runtime.

String name = 'Ana';
// name = null;                 // ❌ compile error
String? nickname;                // nullable: defaults to null
nickname = 'Ace';

4.2 Null-Aware Operators

String? input;
int length = input?.length ?? 0;     // ?. null-aware access, ?? if-null
input ??= 'default';                 // assign only if null
String forced = input!;              // null assertion: throws if null (use sparingly)
List<int>? maybeList;
var first = maybeList?.first;
var copy = [...?maybeList];          // null-aware spread
maybeList?..add(1)..add(2);          // null-aware cascade
var items = [1, ?input?.length];     // null-aware element: omitted if null (Dart 3.8)

4.3 Type Promotion (Flow Analysis)

Promotion works for local variables and private final fields (Dart 3.2+). For public fields or getters, copy them into a local first:

void greet(String? name) {
  if (name == null) {
    print('Hello, stranger');
    return;
  }
  print('Hello, ${name.toUpperCase()}');   // promoted to String
}

int lengthOf(Object value) {
  if (value is String) return value.length;   // promoted to String
  if (value case List list) return list.length;   // pattern promotion (Dart 3)
  return 0;
}
class User {
  String? email;
  void send() {
    final email = this.email;      // local copy enables promotion
    if (email != null) sendTo(email);
  }
}

4.4 required and Null Safety in APIs

void createUser({required String name, String? bio, int age = 18}) {}
createUser(name: 'Ana');

4.5 Best Practices

Prefer non-nullable types. Use ? only when "absence" is meaningful.

Avoid ! unless you can prove non-nullness. Prefer ??, early returns, and pattern checks.

Use late for dependency-injected or lifecycle-initialized fields, not to silence the compiler.