Null errors are incredibly common in nearly all programming languages. These happen when your program attempts to access a variable with a value set to null, throwing an exception during runtime that’s often quite difficult to debug.

You can, of course, explicitly test for null values during compile time using standard if-else statements, and accordingly instruct your program on how to handle each case. This can be very tedious, but it is often the only quality-control option for older languages like Java. Fortunately, more modern languages have introduced null safety – an extremely helpful feature that saves developers time and headaches when it comes to dealing with null references. Languages that do so include Kotlin, Swift, and Dart – which will be the focus of this blog.

Why null safety

The main benefit of null safety is that it provides a way to analyze your code for potential null errors at compile time, usually right in your IDE, rather than deal with a runtime null reference error. It does so by flagging when any non-nullable variable hasn’t been initialized, is being assigned a null, or is being used somewhere that doesn’t allow it.

A big thing about null variables in Dart and Flutter is that you can’t do too much with them. Many functions and objects do not take in null values as parameters, so you’ll see errors flagged at compile time if you attempt to do so. This is by design. Because Dart runs on the end user’s device rather than a server, null reference errors at runtime have the potential to crash the program and ruin the app experience.

Null safety was added to Dart in version 2.12, and now it’s available for all 2.x versions but requires enabling a pubspec setting. Once Dart 3 is released in mid-2023, all versions will have null safety by default.

Implementing null safety in Dart

Dart considers any declared variable “non-nullable” by default. So, unless you take measures to indicate that a value can be set to null, it will always get flagged at compile time. The way you do this in Dart follows a similar syntax convention to Kotlin. Declare non-nullable variables as usual and, for those that can be null, add the nullable type operator “?” after your declaration. Check out the snippet below for a very simple example of the syntax:

//When declaring variables:

// can NEVER be null
int myInteger;
int myInteger = 12;

// can be null
int? myInteger;
int? myInteger = null;

Assigning defaults

Dart provides what’s called the “default” operator to give you a quick way around writing countless if-else statements to check for null values and assign alternatives. You can add the “??” or “??=” operators to a variable or function, thereby ensuring that it will not be null by offering up a “default” value to assign to it. This is very helpful when trying to use variables that are nullable but cannot be null inside of a widget that does not allow for null values as parameters, such as a Text widget.

//Setting the default to 12 if a function returns null
int myValue = someFunction() ?? 12;

//Checking that a variable is null, and if so setting to a default of 12
value = value ?? 2;

//Simplification of the syntax above
value ??= 2

The assertion operator

There may be some cases when you know that a nullable expression won’t ever really be null, but you can’t prove it to the compiler. This might happen when you need to use a variable as a parameter in a widget that does not accept nullables. You can force Dart to treat this variable as non-nullable using the assertion operator (!). This will silence the Dart analyzer’s warning flags.

The assertion operator is risky to use, however, as Dart will throw an exception at runtime if the value you’ve overridden is indeed null.

// declaring a nullable variable, initializing it 
String? companyName = "Embrace";

//using the assertion operator to tell Dart the variable is not null and can be used in the expression
print(companyName!);

// declaring a nullable variable, not initializing it 
String? companyName;

//using the assertion operator here will result in a runtime error
print(companyName!);

Promoting a variable

Newer versions of Dart contain a pretty smart control flow analysis, which enables the compiler to do some helpful things to optimize your code. In the world of nullability, this includes promoting variables.

As we mentioned above, nullable variables are functionally limited in Dart and Flutter. So when Dart’s compiler “promotes” a variable, it essentially reclassifies it as non-nullable so that you can use it in more operations. This happens if you manually check a local variable, like with an if-else statement, and it’s deemed to not be null. Dart understands this and promotes it to be non-nullable.

The “late” keyword

The last implementation detail we’ll touch on is the “late” keyword. This is useful if you’ve declared a non-nullable variable, but haven’t yet initialized it. Using the “late” keyword lets the Dart compiler know that you will be assigning it a value later on in the program before it gets used. However, failing to follow through on this promise will cause Dart to throw an error.

// declaring a late variable
late String companyName;

void main() {
  // assigning value to late variable before it's used
  companyName = "Embrace";
  print(companyName);
}

Performance benefits of null safety

Now that we know how to implement the basics of null safety in Dart, it’s time to talk about the performance benefits.

Of course, having null safety prevents runtime errors that can crash or otherwise badly affect your app. But there are some secondary benefits to having this feature in your development language, including:

  • Smaller binaries
  • Faster native code
  • Better app startup

Null safety, along with Dart’s continuous improvements as a sound type system, means that more of the work to check a program can happen at compile time. Dart’s ahead-of-time (AOT) compiler can make use of a non-nullable type – because Dart is sure that non-nullable values will never be null, it can eliminate many null checks and optimize your code. This means fewer instructions and compensatory code generated by the compiler, smaller binaries, and a lower startup overhead vs. client-side compilation.

Null safety is a great enhancement to Dart, and its implications on code performance are notable. To learn more about Dart and Flutter performance best practices, check out some of our other blogs on the topic.

Embrace can help you diagnose and solve most performance issues with our new Flutter SDK, as it surfaces both Dart-layer and native-layer errors. If you’d like to see how complete mobile visibility can revolutionize how you deliver incredible Flutter experiences, get started with a free trial today.