Dart enforces null safety by default. Variables cannot hold null unless explicitly declared nullable.
Nullable types
String name = 'Dart'; // non-nullable — cannot hold null
// name = null; // compile error
String? nullable = null; // nullable — can hold null
nullable = 'hello'; // okSafe access and operators
String? nullable;
// Null-aware access operator ?.
var length = nullable?.length; // null if nullable is null
// Null-coalescing operator ??
var value = nullable ?? 'default'; // 'default' if null
// Null-coalescing assignment ??=
nullable ??= 'hello'; // assigns only if null
// Null assertion operator ! (throws if null)
var definite = nullable!; // asserts non-null, throws if null
var len = nullable!.length; // use when you know it's not null
// Conditional property access
var upper = nullable?.toUpperCase(); // null if nullable is nullType promotion
Dart automatically promotes nullable types after null checks:
String? maybeName;
if (maybeName != null) {
// maybeName is promoted to String here — no ! needed
print(maybeName.length); // safe
}
// Also works with type checks
Object? value;
if (value is String) {
print(value.length); // promoted to String
}Late initialization
Use late for variables that will be initialized after declaration but before use:
class Service {
late Database db; // will be set before use
void init() {
db = Database();
}
void query() {
db.execute('SELECT ...'); // throws if db was never set
}
}
// Late with initializer — computed lazily on first access
late String description = expensiveCompute();late vs nullable
- Use
latewhen you know the variable will be set before use - Use
?whennullis a valid value at any point
late String name; // will be set later, null is never valid
String? nickname; // null is a valid valueRequired named parameters
Make named parameters non-nullable with required:
// Without required, name could be null
void greet({String? name}) {
print(name);
}
// With required, name must be provided and is non-null
void greetRequired({required String name}) {
print(name);
}
greetRequired(name: 'Ada'); // ok
// greetRequired(); // compile error — name is requiredNullable collections
List<int>? maybeList; // list itself may be null
List<int?> maybeNullItems; // list items may be null
var items = [1, null, 3, null, 5];
items.whereType<int>().toList(); // [1, 3, 5] — filter out nulls
items.nonNulls.toList(); // [1, 3, 5] — Dart 3Null safety in callbacks
// Local variables are promoted in closures only if final
String? name;
// After this check, name is promoted
if (name != null) {
var length = name.length; // ok
}
// For callbacks, use ! or null check
var list = ['a', null, 'b', null, 'c'];
var result = list.where((e) => e != null).map((e) => e!.toUpperCase()).toList();
// Or use nonNulls (Dart 3):
var result2 = list.nonNulls.map((e) => e.toUpperCase()).toList();Common patterns
// Default value
var name = maybeName ?? 'unknown';
// Throw on null
var value = maybeValue ?? throw StateError('value is required');
// Return on null
String process(String? input) {
if (input == null) return 'empty';
return input.toUpperCase();
}
// let-style pattern with cascade
maybeObject?..method1()..method2();
// Ensure non-null in constructors
class Config {
final String host;
final int port;
Config({required this.host, required this.port});
}