Null Safety
Null Check Promotion
Dart's flow analysis narrows a nullable type after a real null check. Inside an if (x != null) { ... } branch the variable is treated as the non-nullable type T, so member access like x.toUpperCase() is a normal call, no ! and no ?. needed. Outside that branch the type goes back to T?. This is the preferred way to handle a value that might actually be missing.
Program
Play the program to check a nullable String? and let flow analysis promote it to String inside the if branch.
null_check_promote.dart
Replay: real traced execution (multi-file project)
void main() {
String? maybeCity = 'Oslo';
String result;
if (maybeCity != null) {
result = maybeCity.toUpperCase();
} else {
result = 'UNKNOWN';
}
print('city=$result');
}
maybeCity ← Oslo (String?)
1void main() {2 String? maybeCity = 'Oslo';3 String result;values this stepOslo (String?)maybeCityresult ← <uninitialized>
2String? maybeCity = 'Oslo';3String result;4if (maybeCity != null) {values this step<uninitialized>resultbranch ← then
3String result;4if (maybeCity != null) {5 result = maybeCity.toUpperCase();values this stepthenbranchOslo (String?)maybeCityresult ← OSLO
4if (maybeCity != null) {5 result = maybeCity.toUpperCase();6} else {values this stepOSLOresultOslo (String)maybeCityprint('city=$result');
8 }9 print('city=$result');10}outputcity=OSLOvalues this stepOSLOresult
flow promotion
Inside `if (maybeCity != null)`, Dart treats `maybeCity` as `String`, not `String?`. Member access like `.toUpperCase()` is a plain call with no null guard.
else branch
Outside the `if`, `maybeCity` is back to `String?`. The `else` arm handles the missing case explicitly so `result` is always assigned before being read.
preferred form
Prefer `if (x != null)` over `x!` when the value can actually be null. The compiler tracks the narrowing so you do not need to write `!` or `?.` inside the branch.