DEV Community

Yohanes Bandung Bondowoso
Yohanes Bandung Bondowoso

Posted on • Originally published at ybbond.id

Non Null Type Inference After Null Checking in Flutter

A shorts.

Suppose I make this cool simple widget with sound null safety:

import 'package:flutter/material.dart';

void main() {
  return CoolWidget();
}

class CoolWidget extends StatelessWidget {
  const CoolWidget({
    this.text,
  });

  final String? text;

  @override
  Widget build(BuildContext build) {
    if (text != null) return Text(text);

    return Text('Y U NO GIVE TEXT?');
  }
}
Enter fullscreen mode Exit fullscreen mode

Flutter will get angry and summon this error log:

Error: The argument type 'String?' can't be assigned to the parameter type 'String' because 'String?' is nullable and 'String' isn't.

if (text != null) return Text(text);
                              ^     
Enter fullscreen mode Exit fullscreen mode

It is silly that Flutter cannot dictate from the null check text != null not a moment ago, and infer that the text is in fact non-nullable. There is only one possible control flow from that point on!

There is bang operator (! operator) to tell Flutter: “Yeah, I know it is typed as nullable, but I am super duper sure it will not be a null!”

Which sounds dangerous, no? Code changes, maintainer changes, the world is changing. There is a great chance that sometime in the future, a refactor done and the maintainer forget to deal with the bang operator.

Solution

After browsing the internet for a bit, I found that there was a mention in the Dart docs:

The analyzer can’t model the flow of your whole application, so it can’t predict the values of global variables or class fields.

which used to be in this page of docs, but currently not exists.

From that documentation text, implied that non-null inference can be done in a local scope.So if we define new local variable and do null check on that var, Flutter will gladly say: “OK homie, peace out ✌️”.

diff --git a/lib/main.dart b/lib/main.dart
index 96969696..69696969 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -13,7 +13,8 @@ class Cool Widget extends StatelessWidget {

  @override 
  Widget build(BuildContext build) {
-   if (text != null) return Text(text);
+   final String? newText = text;
+   if (newText != null) return Text(newText);

    return Text('Y U NO GIVE TEXT?');
  }
Enter fullscreen mode Exit fullscreen mode

Final code looks like this.

import 'package:flutter/material.dart';

void main() {
  return CoolWidget();
}

class CoolWidget extends StatelessWidget {
  const CoolWidget({
    this.text,
  });

  final String? text;

  @override
  Widget build(BuildContext build) {
    final String? newText = text;
    if (newText != null) return Text(newText);

    return Text('Y U NO GIVE TEXT?');
  }
}
Enter fullscreen mode Exit fullscreen mode

Other Example

It’s not just variable (re)declaration, but passing parameter to a method also works.

For example, this will summon error:

import 'package:flutter/material.dart';

void main() {
  return CoolWidget();
}

class CoolWidget extends StatelessWidget {
  const CoolWidget({
    this.text,
  });

  final String? text;

  @override
  Widget build(BuildContext build) {
    return _buildBuildBuild();
  }

  Widget _buildBuildBuild() {
    if (text != null) return Text(text);

    return Text('Y U NO GIVE TEXT?');    
  }
}
Enter fullscreen mode Exit fullscreen mode

You might have guessed that the solution is to pass the parameter to the private method:

diff --git a/lib/main.dart b/lib/main.dart
index 96969696..69696969 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -19,8 +19,8 @@ Widget build(BuildContext build) {
     return _buildBuildBuild();
   }

-   Widget _buildBuildBuild() {
-     if (text != null) return Text(text);
+   Widget _buildBuildBuild({String? newText}) {
+     if (newText != null) return Text(newText);

      return Text('Y U NO GIVE TEXT?'); 
Enter fullscreen mode Exit fullscreen mode

Conclusion

I personally like this approach, rather than using the bang operator (! operator).

Will there be performance impact because we declare some new variables? There might be.

Is passing method parameter counts as declaring new variable? I honestly don’t know. Do tell me if you have insights about this 😃

References

Top comments (0)