Home > OS >  Dart: How to properly handle null-safety?
Dart: How to properly handle null-safety?

Time:10-30

I have upgraded my project to null-safety and it's a bit confusing, because if I wrap into a

if(someObject.field != null) { 
    doSomething(someObject.field);  // error, can't assign String? to String
}

a method call that requires a not-nullable property and my variable I am trying to pass is nullable, then I get a type error that I cannot assign a nullable value to a not-nullable type.

But when I do

String? someObjectField = someObject!.field;
if(someObjectField != null) { 
    doSometing(someObjectField); // Can assign
}

it works as expected.

doSomething = (String foo) {}

For example:

class Person {
  final String name;

  Person(this.name);
  
}

Function test = () {
  Map<String, String?> pers = {
    'name': 'John',
  };

  if(pers['name'] != null) {
    Person(pers['name']); // Error, even if it will be never executed
    Person(pers['name'] as String); // no error
    Person(pers['name']!); // no error
  }
  
};

And if I do something like:

if (widget.event != null && widget.event.featuredImage != null)

Then it complains in the second statement that the receiver (widget.event) can be null and I need to use !, but the second statemen should never execute and it should never cause a runtime exception.

So I need to modify it to:

if (widget.event != null && widget.event!.featuredImage != null)

But then when I try to use a nested widget inside Flutter, then even if I use the if as wrapper I still need to add ! everywhere

Stack(
  children: [
    // Add Container with image only when event and futured image are not null
    if (widget.event != null && widget.event!.featuredImage != null) ...[
      Container(
        height: 250,
        decoration: BoxDecoration(
          color: Colors.transparent,
          image: DecorationImage(
            fit: BoxFit.cover,
            image: NetworkImage(widget.event!.featuredImage!), // here i Need to use ! to satisfy the compiler
          ),
        ),
      ),
    ],
  ],
)

Alternatively I can extract the variable into another one String? image = widget.event!.featuredImage then modify the if statement to if(image != null) and use the widget like NetworkImage(image) which works as expected.

Compared to TypeScript which detects if I checked for null in a condition above, this makes no sense to me.

Longstory short, even if I check for null value to render/not-render a component, I still need to use !.

Is there something obvious that I am missing?

Thank you in advance

CodePudding user response:

Since you compared it to TypeScript, yes, you are missing something.

Typescript is a mess that works in a very limited environment and "works" is grossly overstated. For example you could write a method in typescript that takes a string and then at runtime find out it's not actually a string, surprise, it's a completely different type. The joys of JavaScript. Calling TypeScript "type safe" is correct compared to JS and ridiculous compared to actually compiled languages.

So you are missing the fact that the Dart compiler can guarantee that something is not null once you checked it. To do that, it needs additional constraints. For example, you could have getters that do not return the same value every time you call them. Your call for example could easily return different values between the first and second call, depending on the code of the getter. Or you could use inheritance and polymorphism to build some even more problematic constructs. See here for an interesting example. So you need to have a local variable, that is guaranteed to have the same value, unless explicitely changed.

Your example of if (widget.event != null && widget.event.featuredImage != null) could easily be modified to:

final image = widget?.event?.featuredImage;

Stack(
  children: [
    // Add Container with image only when event and futured image are not null
    if (image != null) ...[
      Container(
        height: 250,
        decoration: BoxDecoration(
          color: Colors.transparent,
          image: DecorationImage(
            fit: BoxFit.cover,
            image: NetworkImage(image), 
          ),
        ),
      ),
    ],
  ],
)

Yes, you have to implement some tiny bit of logic, you cannot just slap ? and ! on your code and it runs as before. But once you understood what that little change in logic is, it is pretty easy.

  • Related