Migrate to Null Safety in Flutter

Prateek Sharma
DKatalis
Published in
10 min readMay 27, 2021

Hey welcome, Glad that you are here.

Flutter team recently announced Flutter 2. It had 2 major highlights — Flutter web stable and sound null safety. In this post, I will take you through all you need to do to migrate to Null Safety in Dart language.

What is Null Safety?

With null safety, compiler enables us to decide whether a variable can be null or not in its lifetime. Before null safety, we could unintentionally introduce an error when trying to access properties of a nullable object. But, with null safety awareness, the developer has more control over this situation and will be notified by the compiler to use null safe operators and omit the Billion Dollar Mistake.

Null Safety in Dart

Well, null safety is not a new term, it has been there in Kotlin since Day 1 and Swift is also a null safe language. When we say these languages are null-safe, it literally means that the underlying language APIs are already null-safe and you have an option to decide your nullable types and the underlying language APIs are already null-safe.

Majorly there are 6 tools that can help you achieve null safety. I strongly believe that only knowing what these operators do will not help in achieving complete null safety in any app. Nobody can reach the deepest of the sea with a dive, they have to swim continuously. For that, you need to understand the tools and techniques. I will quickly walk you through the tools first as there is a lot more to cover😉.

1. Nullable Types

You can make a variable or object nullable by using ? after the type declaration of a variable like String?, int?, Object?, etc. By String?, we instruct the compiler that this type of variable can contain either null or String.

2. Null Aware Operator

This needs no introduction to people coming from null-safe languages like Kotlin. The ?. operator is known as Null aware operator, which can be invoked on only nullable types. For an instance, you’ve got a nullable string String? text, you won’t be allowed to invoke a method on text without using ?.

Using ?. will enforce the compiler to invoke compareTo() only when text is not null.

3. The if null operator ??

Simply putting it with example, the length method will not be called on user if it is null, hence counter remains null. When you then try to add something to counter, compiler throws error as null and a number cannot be added. To avoid, you use default value when the expression returns null. The ?? exactly does this, it executes right side expression of ?? when left side expression is null.

4. Bang Operator! — Casting away Nullability

The ! operator is a way in which we tell the compiler that even though we have declared a variable as nullable, it will not be null when we use it at this place. For an instance:

Personally, there should be no need to use it. These operators will cast the nullable type to non-nullable type and this is a loss of static safety. The cast must be checked at runtime to preserve soundness and it may fail and throw an exception. So, you have to look at your code cautiously before you use this operator.

5. The late keyword

Use the late keyword for a variable when you are initializing it later than declaring it as a field variable.

The variable decided to be lately initialized can be nullable or non-nullable. It can be final as well but can be initialized only once. You can also instruct the compiler to initialize a variable when it is used for the first time. This can boost performance when the initialization is heavy and not needed at the start.

6. The required keyword

The required keyword is used when you want the caller to pass a nullable or non-nullable value to the constructor if it is required. This is not a feature that is introduced specifically for null safety but one of the features that make Dart a more complete language.

Here, the caller will have to pass the nullable value to a, and the non-nullable value to b while initializing. The caller can omit passing c while initializing.

NullSafety ≠ Easy

With the introduction to Flutter 2 and Null Safety, the Dart team has also given us tools like migrate that help in migrating to null safety. So, what else a developer like you has to do? Run this command, sit back and approve the changes that tool suggests? 🤔

NO — That’s not your job. Use the tool but make smart decisions as per your app and use cases. Not every app will be benefitted from the suggestions by this tool.

Why to Migrate?

You must be thinking, why to migrate to null safety. Can you not make apps without Flutter 2? Yes, you can. But, if you have a flutter web app, you should definitely migrate to Fluter 2 as soon as possible because Flutter 2 comes with stable libraries for web support. If you have only mobile application, you can delay this migration as it doesn’t improve on app size. But, null-safety alone is a must have feature to utilize that should pump you up to migrate.

Other Key highlights of Flutter 2.2 are —

  1. Optimization in caching behavior with a new service worker-loading mechanism, and fixed double-downloading of main.dart.js
  2. Better accessibility features for mobile and web
  3. TextSpan gets a mouse cursor change for flutter web
  4. Many more

For example, I tried creating release version of an app with Flutter 1.22.5 stable and Flutter 2.2.0 stable. There was nothing substantial reduction in build size, but unused binaries were removed more compared to previous release.

NullSafety?.isEasy() ⇒ True

Here, I am writing out solutions to all the possible warnings and errors that you might face across while migrating.

Steps to Migrate

  1. Use fvm to easily migrate between flutter versions.
  2. Install fvm by following steps mentioned here — https://github.com/leoafarias/fvm
  3. Switch to flutter 2 version fvm install 2.0.0 and fvm use 2.0.0
  4. Change the IDE settings to use flutter 2 version now. Like in VSCode add settings:

5. Change dart and flutter SDK versions in pubspec.yaml

6. Check the lint errors by the analyze command

Analysis Results

After first analysis, you might get many errors for the plugins that are not updated to null safety versions. We should fix the plugins version first because if we fix code errors first and then update the plugins, you’ll need to again fix code errors. So let’s first upgrade the plugins.

To identify the right versions of the plugins used in the app, run this command -

fvm flutter pub outdated --mode=null-safety

This gives us a list of plugins that are available with null safety versions. Consider yourself lucky, if you see resolvable null-safe version available for all the dependencies that are used in the application. If you don’t have much luck, you can either contact the developer of that plugin or help the developer with MR against their plugin for Flutter 2.

When you have no dependency that is not null safe go ahead and hit the upgrade command -

fvm flutter pub upgrade --null-safety

Run fvm flutter analyze again.

This will reduce many errors for the application. And now comes the hard part, where you pick each lint error and fix it. Let’s see the type of errors one by one and probable solution to them.

dead_null_aware_expression

Description — The left operand can’t be null, so the right operand is never executed.

Before null safety was announced, the ?? operator was already present in the dart language. So, you must have used this operator. But, after introduction of null safety, compiler allows to run ?? only on those objects which are marked as nullable using ?. Refer the below code for more understanding.

In the above code snippet, before null safety use of ?? was totally fine on name of user. But, after null safety this code doesn’t compile. There are 2 solutions for this. My personal favorite is 2nd, as it reduces run time errors.

In Solution 1, remove the ?? check as left side can never be null because name is not declared as non-nullable. This can cause problems when this user is fetched from APIs — name can be null.

In Solution 2, make the name as nullable string by adding ? to the declared type. This solves the purpose.

By the way, if you noticed, we didn’t fix the dead_null_aware_expression by simple removing the ?? , instead we solved the root of problem by introducing nullable variable.

missing_default_value_for_parameter

Description — The parameter can’t have a value of null because of its type, but the implicit default value is null.

This simply suggests that a variable which is supposed to be non-nullable, is not given a default value at the time of declaration.

In the above snippet, we have a User class with non-nullable String which is optional to be defined while creating User instance. This means, we can initialize User without passing name. Before null safety, this is gonna throw Uncaught TypeError: Cannot read property ‘get$length’ of nullError: TypeError: Cannot read property ‘get$length’ of null when log() is invoked.

With Flutter 2 and above, this is gonna give compilation error missing_default_value_for_parameter. So, to fix this we have to decide what we assume name field can consist of.

Solution 1 — If you decide that. name will not be null, use required keyword so that User is always initialized with some name value.

Here is a trick question — What happens when User is initialized with null name. Yes, this is possible not at compile time but at runtime. The API can give name as null and here you will again see the null-pointer error when length is being called on a null object. But, at compile time, the compiler is there to stop you and thats why we need null-safety in the languages.

final user = User(name: null);

Solution 2 — Again our favorite operator, nullable type comes to the rescue. Make name nullable and while accessing name’s length property, compiler guides you to use ?. null-aware operator. Isn’t it nice?

Well, you must be curious to know after making name nullable, what will be printed if name is passed null . This time, null will be printed. If there is a requirement to default the value if null, make use of ?? again as done in previous problem.

unchecked_use_of_nullable_value

Description —The method can’t be unconditionally invoked because the receiver can be null

By this time, we have already seen this. This error is thrown when we try to access property of a nullable object without using ?. null-aware operator.

Here, name is nullable String , which means length property cannot be accessed directly, instead null-aware operator should be used on name .

unnecessary_null_comparison

Description — The operand can’t be null, so the condition is always true.

This error is thrown when we are unnecessary using null-aware operator ?. on objects that are not declared nullable. For an instance —

Before null safety, we access length of name when name is not null. But, with null-safety, compiler understands null check on non-nullable variable unnecessary. So, re-visit the use case whether name should be nullable or non-nullable. If non-nullable, remove the unnecessary null-check or if nullable, use the ?. operator to access length property as we have done above.

not_initialized_non_nullable_variable or not_initialized_non_nullable_instance_field

Description — The non-nullable variables or instance fields must be initialized.

When you are utterly sure that a variable or instance field cannot be null, but you cannot initialize it at the time of declaration. For an instance —

The above snippet compiles on Flutter < 2, but not allowed in Flutter 2 and above. For this, add the late keyword, so that compiler understands that this will be initializes before it is accessed. Notice, this works only on non-nullable variables, because for nullable variables can be left uninitialized as their default value is null.

Summary

To summarise very quickly, we used all the operators that Dart 2.13 has given us. Set some ground rules like what should be nullable and what not for example, you can decide to not pass data to presentation layer if it is null, because if key fields are null from the API response, there is no point in showing them at all. Second rule, will be to make all API response models nullable as there is high chance of API responses to be null. The rest is simple, IDE is gonna always help you with proper messages. And, I must admit the error messages provided by the dart team to understand what needs to be done for certain errors are excellent.

I have explained everything that is needed to migrate to null safety. Thanks for reading till the end. It was a lengthy one, but I believe this tutorial can act as a reference guide to solve errors and find possible solutions based on usecase.

If you loved reading the article, don’t forget to clap 👏. You can reach out to me and follow me on Medium, Twitter, GitHub, YouTube.

--

--