I'm upgrading a personal package that is based on the Flutter framework. I noticed here in the Flutter Text widget source code that there is a null check:
if (textSpan != null) {
properties.add(textSpan!.toDiagnosticsNode(name: 'textSpan', style: DiagnosticsTreeStyle.transition));
}
However, textSpan!
is still using the !
operator. Shouldn't textSpan
be promoted to a non-nullable type without having to use the !
operator? However, trying to remove the operator gives the following error:
An expression whose value can be 'null' must be null-checked before it can be dereferenced. Try checking that the value isn't 'null' before dereferencing it.
Here is a self-contained example:
class MyClass {
String? _myString;
String get myString {
if (_myString == null) {
return '';
}
return _myString; // <-- error here
}
}
I get a compile-time error:
Error: A value of type 'String?' can't be returned from function 'myString' because it has a return type of 'String'.
Or if I try to get _mySting.length
I get the following error:
The property 'length' can't be unconditionally accessed because the receiver can be 'null'.
I thought doing the null check would promote _myString
to a non-nullable type. Why doesn't it?
My question was solved on GitHub so I'm posting an answer below.
Dart engineer Erik Ernst says on GitHub:
Type promotion is only applicable to local variables. ... Promotion of an instance variable is not sound, because it could be overridden by a getter that runs a computation and returns a different object each time it is invoked. Cf. dart-lang/language#1188 for discussions about a mechanism which is similar to type promotion but based on dynamic checks, with some links to related discussions.
So local type promotion works:
String myMethod(String? myString) {
if (myString == null) {
return '';
}
return myString;
}
But instance variables don't promote. For that you need to manually tell Dart that you are sure that the instance variable isn't null in this case by using the !
operator:
class MyClass {
String? _myString;
String myMethod() {
if (_myString == null) {
return '';
}
return _myString!;
}
}
Let's say, this is your code and you're doing a null check on the instance variable and still seeing an error:
class Foo {
int? x;
double toDouble() {
if (x != null) return x.toDouble(); // <-- Error
return -1;
}
}
The method 'toDouble' can't be unconditionally invoked because the receiver can be 'null'.
The error you see in code like this is because Getters are not promoted to their non-nullable counterparts. Let's talk about the reason why.
Let's say, there's a class Bar
which extends Foo
and overrides x
field and implemented like this:
class Bar extends Foo {
@override
int? get x => (++_count).isOdd ? 1 : null;
int _count = 0;
}
Now, if you do
Bar().toDouble();
You would have run into a runtime null error, which is why getters type promotion is prohibited.
We need to cast away nullability from int?
. There are generally 3 ways to do this.
Use local variable (Recommended)
double toDouble() {
final x = this.x; // <-- Use a local variable
if (x != null) return x.toDouble();
return -1;
}
Use ?.
with ??
double toDouble() {
return x?.toDouble() ?? -1; // Provide a default value
}
Use null-assertion operator (!)
You should only use this solution when you're 100% sure that the variable (x
) will never be null
.
double toDouble() {
return x!.toDouble(); // Null assertion operator
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With