mikewhob

Blog

Dart Null Safety: A Complete Migration Guide

· 4 min read · Michael Whobrey
DartFlutterNull SafetyMigration GuideBest PracticesProgramming LanguagesCode Quality

What is Null Safety?

Null safety is a major Dart language feature that helps prevent null reference errors at compile time, making your code more robust and reliable.

It’s designed to eliminate the “billion-dollar mistake” — null pointer exceptions.


Core Concepts

  • Non-nullable by default: Variables can’t be null unless explicitly declared.
  • Nullable types: Use ? to make types nullable.
  • Null-aware operators: Safe ways to handle potentially null values.
  • Flow analysis: Dart tracks null safety through control flow.

Here’s a quick comparison:

FeatureNull Safe DartLegacy Dart
Non-nullable variables✅ Enforced❌ Not enforced
Nullable typesString?String (could be null)
Null checks at compile time
Operators for null safety?., ??, ??=, !Limited

Basic Syntax Changes

Non-nullable Types

// Old way (unsafe)
String name;
int age;

// New way (null safe)
String name = ''; // Must be initialized
int age = 0;      // Must be initialized

// Or use late for lazy initialization
late String name;
late int age;

Nullable Types

// Nullable variables
String? name; // Can be null
int? age;     // Can be null
List<String>? items; // Can be null

// Nullable function parameters
void printName(String? name) {
  if (name != null) {
    print(name.toUpperCase());
  }
}

Null-Aware Operators

Null Check Operator (?.)

String? name;
int length = name?.length ?? 0; // Safe access, returns 0 if null

Null Assignment Operator (??=)

String? name;
name ??= 'Anonymous';
print(name); // 'Anonymous'

Null Assertion Operator (!)

String? name = getName();
String upperName = name!.toUpperCase(); // Assert non-null

⚠️ Caution: Use ! sparingly — only when you are certain the value isn’t null.


Migration Process

Step 1: Update Dependencies

Update your pubspec.yaml and ensure all dependencies support null safety:

dependencies:
  flutter:
    sdk: flutter

Check outdated or unsafe dependencies:

dart pub deps --json | dart pub outdated --json

Step 2: Migrate Your Code

Before Migration:

class User {
  String name;
  int age;
  List<String> hobbies;

  User(this.name, this.age, this.hobbies);

  void addHobby(String hobby) {
    hobbies.add(hobby);
  }
}

After Migration:

class User {
  final String name;
  final int age;
  final List<String> hobbies;

  const User({
    required this.name,
    required this.age,
    required this.hobbies,
  });

  void addHobby(String hobby) {
    hobbies.add(hobby);
  }
}

Common Patterns

Optional Parameters

void createUser({
  required String name,
  int? age,
  String? email,
  List<String>? hobbies,
}) {
  final userAge = age ?? 0;
  final userEmail = email ?? 'no-email@example.com';
  final userHobbies = hobbies ?? <String>[];
}

Factory Constructors

class ApiResponse<T> {
  final T? data;
  final String? error;
  final bool success;

  const ApiResponse._({
    this.data,
    this.error,
    required this.success,
  });

  factory ApiResponse.success(T data) {
    return ApiResponse._(data: data, success: true);
  }

  factory ApiResponse.error(String error) {
    return ApiResponse._(error: error, success: false);
  }
}

Best Practices

Prefer non-nullable types whenever possible ✅ Use required parameters for clarity ✅ Handle null gracefully with defaults and null-aware operators

Example:

String getUserDisplayName(User? user) {
  return user?.name ?? 'Anonymous User';
}

Testing Null Safety

Unit tests can validate null-safe behavior:

void main() {
  group('Null Safety Tests', () {
    test('should handle null values correctly', () {
      String? name;
      expect(name?.length, isNull);
      expect(name ?? 'default', equals('default'));
    });

    test('should work with nullable collections', () {
      List<String>? items;
      expect(items?.isEmpty, isNull);

      items = [];
      expect(items?.isEmpty, isTrue);
    });
  });
}

Conclusion

Dart null safety is a powerful upgrade that:

  • Eliminates entire classes of runtime errors
  • Improves developer confidence
  • Makes APIs clearer and more reliable

➡️ Start migration with small, isolated parts of your codebase. ➡️ Use Dart’s migration tools. ➡️ Remember: null safety isn’t just about adding ? and ! — it’s about designing APIs that communicate intent clearly.

Enjoyed this? Share it, or reply by email — comments are retired here to keep the site fast and low-maintenance.