Skip to content

Null Safety

Null safety is one of Dart's most important features, introduced in Dart 2.12. It helps prevent null reference errors, which are among the most common programming mistakes.

Understanding Null Safety

In Dart with null safety, variables are non-nullable by default. This means they cannot contain null unless explicitly declared as nullable.

dart
// Non-nullable (default)
String name = 'Alice';
// name = null;  // Error: Can't assign null to non-nullable variable

// Nullable (with ?)
String? nullableName = 'Bob';
nullableName = null;  // OK

Nullable Types

Add ? after the type to make it nullable:

dart
int? age;           // Can be int or null
String? name;       // Can be String or null
List<int>? numbers; // Can be List<int> or null

Non-Nullable Types

Without ?, variables must have a value:

dart
int count = 0;      // Must be initialized
String message;     // Error: Must be initialized

void main() {
  message = 'Hello';  // OK after initialization
}

Null-aware Operators

Null-coalescing Operator (??)

Provides a default value if the expression is null:

dart
String? name;
String displayName = name ?? 'Guest';
print(displayName);  // Guest

int? score;
int finalScore = score ?? 0;
print(finalScore);  // 0

Null-aware Assignment (??=)

Assigns a value only if the variable is null:

dart
String? name;
name ??= 'Default';
print(name);  // Default

name ??= 'Another';
print(name);  // Default (not changed)

Null-aware Access (?.)

Safely access properties or methods:

dart
class Person {
  String? name;
  int? age;
}

Person? person;

// Safe access
print(person?.name);  // null (no error)
print(person?.age);   // null

person = Person();
person.name = 'Alice';
print(person?.name);  // Alice

Null Assertion Operator (!)

Asserts that a value is not null:

dart
String? nullable = 'Hello';
String nonNull = nullable!;  // Assert non-null
print(nonNull);  // Hello

// Throws error if null
String? nullValue;
// String error = nullValue!;  // Runtime error!

⚠️ Warning: Only use ! when you're absolutely certain the value isn't null.

Late Variables

Use late for variables that will be initialized later but before use:

dart
late String description;

void initialize() {
  description = 'Initialized';
}

void main() {
  initialize();
  print(description);  // OK
}

// Late with initializer (lazy initialization)
late String heavyComputation = expensiveOperation();

String expensiveOperation() {
  print('Computing...');
  return 'Result';
}

void main() {
  print('Before');
  print(heavyComputation);  // Computing... Result
  print(heavyComputation);  // Result (not computed again)
}

Null Safety in Functions

Return Types

dart
// Non-nullable return
String getName() {
  return 'Alice';
  // return null;  // Error
}

// Nullable return
String? findUser(int id) {
  if (id == 1) {
    return 'Alice';
  }
  return null;  // OK
}

Parameters

dart
// Non-nullable parameter
void greet(String name) {
  print('Hello, $name');
}

// Nullable parameter
void greetOptional(String? name) {
  print('Hello, ${name ?? 'Guest'}');
}

void main() {
  greet('Alice');
  // greet(null);  // Error
  
  greetOptional('Bob');
  greetOptional(null);  // OK
}

Type Promotion

Dart automatically promotes nullable types to non-nullable after null checks:

dart
String? name = 'Alice';

if (name != null) {
  // name is promoted to String (non-nullable)
  print(name.length);  // OK, no need for ?
}

// Using is check
Object? value = 'Hello';
if (value is String) {
  print(value.length);  // value promoted to String
}

Null Safety with Collections

dart
// Non-nullable list
List<String> names = ['Alice', 'Bob'];
// names.add(null);  // Error

// Nullable elements
List<String?> nullableNames = ['Alice', null, 'Bob'];
nullableNames.add(null);  // OK

// Nullable list
List<String>? maybeList;
print(maybeList?.length);  // null

maybeList = ['Alice'];
print(maybeList?.length);  // 1

Handling Nullable Values

Pattern 1: Provide Default

dart
String? input;
String message = input ?? 'No input';

Pattern 2: Null Check

dart
String? name;

if (name != null) {
  print(name.toUpperCase());
}

Pattern 3: Safe Call

dart
String? name;
print(name?.toUpperCase());  // null if name is null

Pattern 4: Assert Non-null

dart
String? name = getName();
print(name!.toUpperCase());  // Use only if certain it's not null

Null Safety Best Practices

  1. Prefer non-nullable types whenever possible
  2. Use late sparingly - only when necessary
  3. Avoid ! unless you're absolutely certain
  4. Use ?. for safe navigation
  5. Provide defaults with ??
  6. Initialize variables at declaration when possible
  7. Use type promotion instead of repeated null checks

Migration to Null Safety

If migrating old code:

dart
// Before null safety
String name;
name = null;  // Was OK

// After null safety
String? name;  // Must be explicitly nullable
name = null;  // Now OK

Complete Example

dart
class User {
  final String id;
  final String name;
  String? email;
  int? age;
  
  User({
    required this.id,
    required this.name,
    this.email,
    this.age,
  });
  
  String getDisplayName() {
    return name;
  }
  
  String? getEmail() {
    return email;
  }
  
  String getEmailOrDefault() {
    return email ?? 'no-email@example.com';
  }
  
  void printInfo() {
    print('ID: $id');
    print('Name: $name');
    print('Email: ${email ?? 'Not provided'}');
    print('Age: ${age ?? 'Not provided'}');
    
    // Safe navigation
    print('Email length: ${email?.length ?? 0}');
    
    // Type promotion
    if (age != null) {
      print('Age in 5 years: ${age + 5}');
    }
  }
}

void main() {
  // Non-nullable fields must be provided
  User user1 = User(
    id: '1',
    name: 'Alice',
    email: 'alice@example.com',
    age: 25,
  );
  
  user1.printInfo();
  
  // Nullable fields can be omitted
  User user2 = User(
    id: '2',
    name: 'Bob',
  );
  
  user2.printInfo();
  
  // Null-aware operations
  String? searchQuery;
  String query = searchQuery ?? 'default';
  print('Search query: $query');
  
  // Late variable
  late String config;
  config = 'Loaded configuration';
  print(config);
}

Next Steps

Content is for learning and research only.