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

2

6

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.

Trefoil answered 22/4, 2022 at 8:49 Comment(1)
For those who are wondering what else can be done if you don't want to use null check operators, you can assign the global variable to a local variable inside your build function like final user = this.user;. I am currently using this approach and I believe it is reasonable for accessing its properties like if (user != null) { user.name; }.Trefoil
W
8

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);       
  }
}
Washko answered 22/4, 2022 at 9:50 Comment(0)
R
1

These are for sound null safety. Thats why whenever you start calling a functions / accessing it makes sure that the variable is not null by using ! or provide other case for null using ?.

Suppose for following case :

    if (string != null) {
        string=null; // Or through other function xyx() {string=null;} string becomes null then your if condition is void
        print(string.length);
        print(string.isNotEmpty);
        print(string.trim());
        print(string.contains('x'));
      }

// Still sound null safety that's why above is not allowed
    if (string != null) {
        string=null; // Or through other function xyx() {string=null;} string becomes null then your if condition is void
        print(string!.length);
        print(string!.isNotEmpty);
        print(string!.trim());
        print(string!.contains('x'));
      }

So for sound null safety it is required to be checked if string is not null before accessing it.

As per your comments you need to assign this nullable string to a non nullable string (~isdatablank~ means String was null) and proceed

String? string;
  String s=string??"~isdatablank~";
  if (s != "~isdatablank~") {
        print(s.length);
        print(s.isNotEmpty);
        print(s.trim());
        print(s.contains('x'));
      }
Ritaritardando answered 22/4, 2022 at 9:6 Comment(4)
But shouldn't the compiler understand that I didn't assign anything to the variable before accessing its fields or methods?Trefoil
There might be some design considerations thought by them. One use case i can think of is if someone is using bloc pattern and his observer is inside this function then on change of value, only the observer widget will be refreshed and then it can be null then. There is not necessarily always direct assignments. These can be through bloc pattern etc too. Plus if you call some other function which indirectly updates string value then there is no way for compiler to identify it. like some function xyz() that what i said in comment too in code.Ritaritardando
Okay, let's say I'm sure that there are no asynchronus interrupts that makes the variable null again, isn't there at least a way to use the null check (!) operator once in a function or function hierarchy so I don't need to type it repeatedly? Something like a guide for me to understand that the variable is not null in a specific code block?Trefoil
The if is perfectly fine here, you don't need ? or ! operators or any of your workarounds. It just doesn't work with variables that are not exclusive to the scope of the if.Washko

© 2022 - 2024 — McMap. All rights reserved.