Search code examples
flutterdartdart-null-safety

How can I make null-safety assertions to avoid using null check (!) or conditional (?) operators in Flutter?


Dart compiler does not understand that the variable can not be null when I use it inside an if (x != null) statement. It still requires to use conditional ? or null check ! operators to access one of the variable's fields or methods.

Here is an example:

String? string;

void test() {
  if (string != null) {
    print(string.length);
  }
}

This produces a compile-time error and says that

The property 'length' can't be unconditionally accessed because the receiver can be 'null'. Try making the access conditional (using '?.') or adding a null check to the target ('!').

However, the receiver actually can't be null since it's wrapped with if (string != null) block. Accessing the field with string?.length or string!.length works fine but it can be confusing when I need to use different fields or methods of the variable.

String? string;

void test() {
  if (string != null) {
    print(string.length);
    print(string.isNotEmpty);
    print(string.trim());
    print(string.contains('x'));
  }
}

All of these statements raise the same error. I also tried putting assert(string != null); but the compiler still does not understand that the string is not null.

To give a more sophisticated example;

User? user;

@override
Widget build(BuildContext context) {
  if (user == null) {
    return Text('No user available');
  } else {
    return buildUserDetails();
  }
}

Widget buildUserDetails() {
  return Column(
    children: [
      Text(user.name),
      Text(user.email),
    ],
  );
}

This is still a problem for the compiler.


Solution

  • However, the receiver actually can't be null since it's wrapped

    And that assumption is plain wrong.

    Your variable is a global variable and any other part of your program, through multi-threading or other shenanigans can slip in between your if and the next line and change the variable.

    That is why only local variables can be promoted to their non-null equivalent when the compiler proves that they cannot be null in certain code execution branches like an if.

    The following will work perfectly fine, because you are operating on a local variable that the compiler can be sure won't be changed by outside operations:

    String? string;
    
    void test() {
      final local = string;
      if (local != null) {
    
        // here, local was promoted from "string?" to "string"
        // since the "if" makes sure it is not null AND
        // the variable is not accessible to anything but this
        // function, so it cannot be changed from the outside
        // and is contrained to the sequential flow of this method.
    
        print(local.length);       
      }
    }