34  null Guideline

Optional parameters must be nullable or have a default value. You can use required to make named parameters non-optional. Non-nullable top-level variables and static fields must have initializers. Non-nullable instance fields must be initialized before the constructor body begins.

Let me break down this Dart documentation into clear sections with examples:

34.1 Optional Parameters Must Be Nullable or Have Default Values

Nullable optional parameters:

void greetUser(String name, [String? title]) {
  print('Hello ${title ?? ''} $name');
}

// Usage
greetUser('John'); // title is null
greetUser('John', 'Dr.'); // title is 'Dr.'

Optional parameters with default values:

void greetUser(String name, [String title = 'Mr.']) {
  print('Hello $title $name');
}

// Usage
greetUser('John'); // Uses default 'Mr.'
greetUser('John', 'Dr.'); // Uses 'Dr.'

❌ This won’t work (non-nullable without default):

void greetUser(String name, [String title]) { // ERROR!
  print('Hello $title $name');
}

34.2 Using required for Named Parameters

Without required (optional and nullable):

class User {
  String name;
  String? email; // Optional, can be null
  
  User({required this.name, this.email});
}

// Usage
User user1 = User(name: 'John'); // email is null
User user2 = User(name: 'John', email: 'john@example.com');

With required (mandatory named parameters):

class User {
  String name;
  String email;
  
  User({required this.name, required this.email});
}

// Usage
User user = User(name: 'John', email: 'john@example.com'); // Both required
// User user = User(name: 'John'); // ERROR! email is required

34.3 Non-nullable Top-level Variables and Static Fields Must Have Initializers

Top-level variables:

// ✅ Correct - initialized
String appName = 'MyApp';
int version = 1;

// ❌ Error - not initialized
String appName; // ERROR!

Static fields:

class AppConfig {
  // ✅ Correct - initialized
  static String appName = 'MyApp';
  static int maxUsers = 100;
  
  // ❌ Error - not initialized
  static String version; // ERROR!
}

Late initialization (when you can’t initialize immediately):

late String configPath; // Will be set before first use

void main() {
  configPath = '/path/to/config'; // Must set before using
  print(configPath); // Safe to use now
}

34.4 Non-nullable Instance Fields Must Be Initialized Before Constructor Body

Direct initialization:

class User {
  String name = 'Unknown'; // Initialized at declaration
  int age = 0;
}

Constructor parameter initialization:

class User {
  String name;
  int age;
  
  // ✅ Correct - initialized in constructor signature
  User(this.name, this.age);
}

Initializer list:

class User {
  String name;
  String email;
  String displayName;
  
  // ✅ Correct - initialized before constructor body
  User(this.name, this.email) : displayName = name.toUpperCase();
}

❌ This won’t work (initialized in constructor body):

class User {
  String name; // ERROR! Not initialized
  
  User(String inputName) {
    name = inputName; // Too late! Must be initialized before body
  }
}

Using late for complex initialization:

class DatabaseConnection {
  late String connectionString;
  
  DatabaseConnection(String host, int port) {
    // Complex logic that can't be done in initializer list
    connectionString = 'postgresql://$host:$port/mydb';
  }
}

34.5 Real-world Example Combining All Concepts

// Top-level variable - must be initialized
String appVersion = '1.0.0';

class ApiService {
  // Static field - must be initialized
  static String baseUrl = 'https://api.example.com';
  
  // Instance fields - must be initialized before constructor body
  String apiKey;
  String? userAgent; // Nullable, so no initializer required
  late HttpClient client; // Will be initialized in constructor body
  
  // Constructor with required and optional parameters
  ApiService({
    required this.apiKey, // Required named parameter
    this.userAgent, // Optional nullable parameter
    String timeout = '30s', // Optional with default value
  }) {
    // Complex initialization that couldn't be done in initializer list
    client = HttpClient()
      ..connectionTimeout = Duration(seconds: int.parse(timeout.replaceAll('s', '')))
      ..userAgent = userAgent ?? 'MyApp/$appVersion';
  }
  
  // Method with optional parameters
  Future<String> fetchData(String endpoint, [Map<String, String>? headers]) async {
    // Implementation here
    return 'data';
  }
}

The core principle is that Dart’s null safety system ensures all non-nullable variables have values before they can be used, preventing null reference errors at compile time rather than runtime.