Null Safety
## Learning Objectives
- Understand null safety in Dart
- Master nullable types
- Use null-aware operators
- Work with late variables
## Why Null Safety?
### The Problem
```dart
// Without null safety
String? name; // Can be null
print(name.length); // Runtime error if null!
```
### Dart's Solution
```dart
// With null safety - caught at compile time
String name = 'Alice'; // Cannot be null
// name = null; // Error!
String? optional; // Nullable
optional = null; // OK
// print(optional.length); // Error! Can't access without null check
```
## Nullable Types
### Basic Syntax
```dart
void main() {
// Non-nullable (must have value)
String name = 'Alice';
// Nullable (can be null)
String? nullable;
nullable = null; // OK
nullable = 'Bob'; // OK
}
```
### Null Check Required
```dart
void main() {
String? nullable;
// Error: nullable can't be assigned to non-nullable
// String mustNotBeNull = nullable;
// Solution 1: Check for null
if (nullable != null) {
String definitelyNotNull = nullable; // OK
print(definitelyNotNull.length);
}
// Solution 2: Use nullable directly with operators
print(nullable?.length); // null (safe access)
}
```
## Null-Aware Operators
### Null-aware Access (?.)
```dart
void main() {
String? name;
print(name?.length); // null (doesn't throw)
name = 'Alice';
print(name?.length); // 5
}
```
### Null-aware Assignment (??=)
```dart
void main() {
String? name;
name ??= 'Default'; // Only assigns if null
print(name); // Default
name = 'Alice';
name ??= 'Default'; // No effect since not null
print(name); // Alice
}
```
### Null-aware Index ([]?)
```dart
void main() {
List? numbers = [1, 2, 3];
print(numbers?[0]); // 1
numbers = null;
print(numbers?[0]); // null
}
```
### If-null Operator (??)
```dart
void main() {
String? name;
// Returns left if not null, otherwise right
String displayName = name ?? 'Guest';
print(displayName); // Guest
name = 'Alice';
displayName = name ?? 'Guest';
print(displayName); // Alice
}
```
### Null-aware Method Invocation
```dart
void main() {
String? text;
// Only invokes if not null
text?.toUpperCase(); // null
text = 'hello';
text?.toUpperCase(); // HELLO
}
```
## Force Unwrap (!)
### When to Use
```dart
void main() {
String? name = 'Alice';
// Use ! when you're certain it's not null
// Only use if you've checked or know it's guaranteed
print(name!.length); // 5
// Danger!
String? danger;
// print(danger!.length); // Runtime error!
}
```
### Pattern for Known Non-null
```dart
void main() {
String? compute() {
// ... complex logic that always returns non-null
return 'result';
}
String result = compute()!; // Safe if you trust the logic
print(result.length);
}
```
## Late Variables
### Basic Usage
```dart
late String description;
void init() {
description = 'Initialized';
}
void main() {
// print(description); // Error if accessed before init()
init();
print(description); // Initialized
}
```
### With Final
```dart
late final String config;
void load() {
config = 'Loaded';
}
void main() {
load();
print(config); // Loaded
}
```
### Lazy Initialization
```dart
late String expensive = compute();
String compute() {
print('Computing...');
return 'Result';
}
void main() {
// expensive not computed yet
print('Before access');
print(expensive); // Computing... Result
}
```
### Use Cases
```dart
class User {
late String displayName;
User(Map data) {
// Expensive operation deferred
displayName = data['name'] ?? 'Unknown';
}
}
```
## Null Safety in Functions
### Parameters
```dart
// Nullable parameter
void greet(String? name) {
if (name != null) {
print('Hello, $name');
}
}
// Required non-null
void greetRequired(String name) {
print('Hello, $name');
}
void main() {
greet(null); // OK
greet('Alice'); // OK
// greetRequired(null); // Error!
greetRequired('Alice'); // OK
}
```
### Return Types
```dart
// Nullable return
String? findName() {
return 'Alice';
// or return null;
}
// Non-null return (must always return value)
String findGreeting() {
return 'Hello';
}
```
## Null Safety in Classes
### Nullable Fields
```dart
class User {
String name;
String? email; // Nullable
User(this.name, [this.email]);
}
void main() {
var user = User('Alice', 'alice@email.com');
print(user.email); // alice@email.com
var user2 = User('Bob');
print(user2.email); // null
}
```
### Late Fields
```dart
class DataService {
late String data;
Future load() async {
data = await fetchData();
}
}
```
### Nullable Fields with Late
```dart
class Config {
late String? cachedData;
void load() {
cachedData = fetch();
}
String? get() {
return cachedData;
}
}
```
## Null Safety in Collections
### Nullable Elements
```dart
void main() {
// List of nullable strings
List items = ['a', null, 'b', null, 'c'];
for (var item in items) {
if (item != null) {
print(item.toUpperCase());
}
}
}
```
### Nullable Collection
```dart
void main() {
// Nullable list
List? nullableList;
print(nullableList?.length); // null
nullableList = [1, 2, 3];
print(nullableList?.length); // 3
}
```
## Null Safety in Generics
### Nullable Type Arguments
```dart
void main() {
// List - list of nullable strings
List nullableStrings = ['a', null, 'b'];
// List? - nullable list of strings
List? optionalList;
// Map - nullable keys and values
Map mixed = {null: 0, 'a': 1};
}
```
## Dart 3 Non-nullable by Default
### Required Named Parameters
```dart
void createUser({
required String name,
String? email, // Optional
int age = 0,
}) {
print('$name, $email, $age');
}
void main() {
createUser(name: 'Alice'); // OK
// createUser(); // Error: name required
}
```
## Best Practices
### 1. Prefer Non-nullable Types
```dart
// Bad
String? name = 'Alice';
// Good
String name = 'Alice';
```
### 2. Use Late Sparingly
```dart
// Only when necessary
late String data;
// Better if you can use constructor
Data(this.data); // preferred
```
### 3. Use Null Checks
```dart
// Before accessing nullable
if (value != null) {
value.someMethod();
}
// Or use safe operators
value?.someMethod();
```
### 4. Provide Defaults
```dart
// With ?? operator
String display = name ?? 'Unknown';
// With ??= for mutable
name ??= 'Default';
```
## Summary
- Dart is null-safe by default
- `?` suffix makes type nullable
- `?.` safe property access
- `??` if-null operator
- `??=` null-aware assignment
- `!` force unwrap (use carefully)
- `late` defers initialization
- Null safety prevents runtime null errors at compile time
Comments
Comments powered by Giscus
To enable comments, add your Giscus embed code here.
Learn more about Giscus →