Dart DISCUSSION

Dart null safety: why is my nullable field not promoted after a null check, and when is ! safe?

Started by muhammadsyafiq Dart null safetytype promotionnull check operatorlate keywordnullable fields
5 replies 248 views 6 participants
Latest activity · 30 Sep 2026

Dart null safety: why is my nullable field not promoted after a null check, and when is ! safe?

muhammadsyafiq Dart Forum
#1

In a Dart class I have String? deviceName;. Inside a method, if (deviceName != null) { print(deviceName.length); } gives an error that the property cannot be accessed because the receiver can be null, although the same check works on a local variable. Adding ! fixes the compile error, but elsewhere in the app I then hit "Null check operator used on a null value" at runtime.

How do promotion, !, ?., ?? and late fit together, and what is the clean way to write this?

Community replies 5

Re: Dart null safety: why is my nullable field not promoted after a null check, and when is ! safe?

#2

With sound null safety, String can never hold null and String? can. After a null check on a local variable the compiler promotes it to the non-nullable type, because it can see every assignment to that variable. A field is different: another method could set it to null between the check and the use, and a subclass could override the field with a getter that returns a different value on each read.

Since the compiler cannot rule that out, a public or non-final field is never promoted. Newer Dart versions do promote private final fields, which cannot change or be overridden from outside the library.

Re: Dart null safety: why is my nullable field not promoted after a null check, and when is ! safe?

#3

The clean fix is a local copy: final name = deviceName; then if (name != null) print(name.length);. The local is promoted and the code reads the field only once.

For simple expressions use the null-aware operators. deviceName?.length evaluates to null if the field is null. deviceName ?? 'unknown' supplies a fallback. deviceName ??= 'unnamed' assigns only when the field is currently null. Combined: final n = deviceName?.length ?? 0;.

Re: Dart null safety: why is my nullable field not promoted after a null check, and when is ! safe?

#4

! converts a nullable expression to non-nullable and throws if the value is null; that throw is the "Null check operator used on a null value" message. It moves the check from compile time to runtime, so every ! is a place where the app can crash. It is justified when you know something the compiler cannot, and even then an explicit check with a meaningful error is friendlier to debug.

Common sources in Flutter code are map[key]! (map lookups always return a nullable type, since the key may be absent) and using a GlobalKey's currentState! before the widget has been built.

Re: Dart null safety: why is my nullable field not promoted after a null check, and when is ! safe?

#5

late is for a non-nullable variable that is initialised after its declaration, for example late final StreamSubscription sub; assigned in initState. You get a non-nullable type without ? everywhere, but the check again moves to runtime: reading it before assignment throws LateInitializationError. With an initialiser, late final table = buildTable(); is computed lazily on first access.

Do not use late to silence the compiler for values that may really be absent. If "no device selected yet" is a normal state, a nullable field handled with the operators above models it correctly.

Re: Dart null safety: why is my nullable field not promoted after a null check, and when is ! safe?

#6

Design helps more than operators. Make constructor parameters required and fields non-nullable wherever a value always exists, so nullability stays at the edges: parsing input, optional settings, results of lookups. Convert at that edge once, with a default or an early return, and the rest of the code works with non-nullable types and needs no checks.

To audit an existing project, search for !., !) and !;. Each hit is either provably safe or a crash waiting for the right input, and replacing them with a local check usually takes a line or two.

TEP COMMUNITY