"The operator can’t be unconditionally invoked because the receiver can be null" error after migrating to Dart null-safety

19,205

Solution 1

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!;
  }
}

Solution 2

The Error:

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? i = 0;

  double func() {
    if (i != null) return i.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.


Reason of the Error:

Let's say, there's a class Bar which extends Foo and override i variable and doesn't assign it any value (keeping it null):

class Bar extends Foo {
  @override
  int? i;
}

So, if you could do

print(Bar().func() * 2);

You would have run into a runtime null error, which is why getters type promotion is prohibited.


Solutions:

We need to cast away nullability from int?. There are generally 3 ways to do this (more ways include the use of as, is, etc)

  • Use local variable (Recommended)

    double bar() {
      var i = this.i; // <-- Use of local variable.
      if (i != null) return i.toDouble();
      return -1;
    }
    
  • Use ?. with ??

    double bar() {
      return i?.toDouble() ?? -1; // Provide some default value.
    }
    
  • Use Bang operator (!)

    You should only use this solution when you're 100% sure that the variable (i) will never be null.

    double bar() {
      return i!.toDouble(); // <-- Bang operator in play.
    }
    
Share:
19,205
Suragch
Author by

Suragch

Read my story here: Programming was my god

Updated on June 10, 2022

Comments

  • Suragch
    Suragch about 2 years

    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.