This page describes how and when to migrate your code to null safety. Here are the basic steps for migrating each package that you own:
- Wait for the packages that you depend on to migrate.
- Migrate your package’s code, preferably using the interactive migration tool.
- Statically analyze your package’s code.
- Test to make sure your changes work.
- If the package is already on pub.dev, publish the null-safe version as a prerelease version.
For an informal look at the experience of using the migration tool, watch this video:
1. Wait to migrate
We strongly recommend migrating code in order, with the leaves of the dependency graph being migrated first. For example, if package C depends on package B, which depends on package A, then A should be migrated to null safety first, then B, then C.
Although you can migrate before your dependencies support null safety, you might have to change your code when your dependencies migrate. For example, if you predict that a function will take a nullable parameter but the package migrates it to be non-nullable, then passing a nullable argument becomes a compile error.
This section tells you how to
check and update your package’s dependencies,
with the help of the dart pub outdated
command in null-safety mode.
The instructions assume your code is under source control,
so that you can easily undo any changes.
Switch to the latest beta release
Switch to the latest beta release of either the Dart SDK or the Flutter SDK. How you get the latest beta release depends on whether you use the Flutter SDK:
-
If you use the Flutter SDK, switch to the beta channel:
$ flutter channel beta $ flutter upgrade
-
Otherwise, download a beta release from the Dart SDK archive.
Check dependency status
Get the migration state of your package’s dependencies, using the following command:
$ dart pub outdated --mode=null-safety
If the output says that all the packages support null safety, then you can start migrating. Otherwise, use the Resolvable column to find null-safe releases, if they exist.
Here’s an example of the output for a simple package. The green checkmarked version for each package supports null safety:
The output shows that all of the package’s dependencies have resolvable prereleases that support null safety.
If any of your package’s dependencies don’t yet support null safety, we encourage you to reach out to the package owner. You can find contact details on the package page on pub.dev.
Update dependencies
Before migrating your package’s code, update its dependencies to null-safe versions:
-
Run
dart pub upgrade --null-safety
to upgrade to the latest versions supporting null safety. Note: This command changes yourpubspec.yaml
file. -
Run
dart pub get
.
2. Migrate
Most of the changes that your code needs to be null safe
are easily predictable.
For example, if a variable can be null
,
its type needs a ?
suffix.
A named parameter that shouldn’t be nullable
needs to be marked required
.
You have two options for migrating:
- Use the migration tool, which can make most of the easily predictable changes for you.
- Migrate your code by hand.
Using the migration tool
The migration tool takes a package of null-unsafe Dart code and converts it to null safety. You can guide the tool’s conversion by adding hint markers to your Dart code.
Before starting the tool, make sure you’re ready:
- Use the latest 2.12 beta release of the Dart SDK.
- Use
dart pub outdated --mode=null-safety
to make sure that all dependencies are null safe and up-to-date.
Start the migration tool by running the dart migrate
command
in the directory that contains the package’s pubspec.yaml
file:
$ dart migrate
If your package is ready to migrate, then the tool produces a line like the following:
View the migration suggestions by visiting:
http://127.0.0.1:60278/Users/you/project/mypkg.console-simple?authToken=Xfz0jvpyeMI%3D
Visit that URL in a Chrome browser to see an interactive UI where you can guide the migration process:
For every variable and type annotation,
you can see what nullability the tool infers.
For example, in the preceding screenshot,
the tool infers that the ints
list (previously a list of int
)
in line 1 is nullable, and thus should be a list of int?
.
Understanding migration results
To see the reasons for each change (or non-change), click its line number in the Proposed Edits pane. The reasons appear in the Edit Details pane.
For example, consider the following code, from before null safety:
var ints = const <int>[0, null];
var zero = ints[0];
var one = zero + 1;
var zeroOne = <int>[zero, one];
The default migration when this code is outside a function (it’s different within a function) is backward compatible but not ideal:
var ints = const <int?>[0, null];
var zero = ints[0];
var one = zero! + 1;
var zeroOne = <int?>[zero, one];
By clicking the line 3 link,
you can see the migration tool’s reasons for
adding the !
.
Because you know that zero
can’t be null,
you can improve the migration result.
Improving migration results
When analysis infers the wrong nullability, you can override its proposed edits by inserting temporary hint markers:
-
In the Edit Details pane of the migration tool, you can insert hint markers using the Add
/*?*/
hint and Add/*!*/
hint buttons.These buttons add comments to your file immediately, and there’s no Undo. If you don’t want a hint that the tool inserted, you can use your usual code editor to remove it.
-
You can use an editor to add hint markers, even while the tool is still running. Because your code hasn’t opted into null safety yet, you can’t use new null-safety features. You can, however, make changes like refactoring that don’t depend on null-safety features.
When you’ve finished editing your code, click Rerun from sources to pick up your changes.
The following table shows the hint markers that you can use to change the migration tool’s proposed edits.
Hint marker | Effect on the migration tool |
---|---|
expression /*!*/ |
Adds a ! to the migrated code, casting expression to its underlying non-nullable type. |
type /*!*/ |
Marks type as non-nullable. |
/*?*/ |
Marks the preceding type as nullable. |
/*late*/ |
Marks the variable declaration as late , indicating that it has late initialization. |
/*late final*/ |
Marks the variable declaration as late final , indicating that it has late, one-time initialization. |
/*required*/ |
Marks the parameter as required . |
A single hint can have ripple effects elsewhere in the code.
In the example from before,
manually adding a /*!*/
marker where zero
is assigned its value (on line 2)
makes the migration tool infer the type of zero
as int
instead of int?
.
This type change can affect code that directly or indirectly uses zero
.
var zero = ints[0]/*!*/;
With the above hint, the migration tool changes its proposed edits,
as the following code snippets show.
Line 3 no longer has a !
after zero
,
and in line 4 zeroOne
is inferred to be
a list of int
, not int?
.
First migration | Migration with hint |
---|---|
|
|
Applying changes
When you like all of the changes that the migration tool proposes, click Apply migration. The migration tool deletes the hint markers and saves the migrated code. The tool also updates the minimum SDK constraint in the pubspec, which opts the package into null safety.
The next step is to statically analyze your code. If it’s valid, then test your code. Then, if you’ve published your code on pub.dev, publish a null-safe prerelease.
Migrating by hand
If you prefer not to use the migration tool, you can migrate manually.
We recommend that you first migrate leaf libraries — libraries that don’t import other files from the package. Then migrate libraries that directly depend on the leaf libraries. End by migrating the libraries that have the most intra-package dependencies.
For example, say you have a lib/src/util.dart
file
that imports other (null-safe) packages and core libraries,
but that doesn’t have any import '<local_path>'
directives.
Consider migrating util.dart
first,
and then migrating simple files that depend only on util.dart
.
If any libraries have cyclic imports
(for example, A imports B which imports C, and C imports A),
consider migrating those libraries together.
To migrate a package by hand, follow these steps:
- Edit the package’s
pubspec.yaml
file, setting the minimum SDK constraint to2.12.0-0
:environment: sdk: '>=2.12.0-0 <3.0.0'
-
Regenerate the package configuration file:
$ dart pub get
Running
dart pub get
with a lower SDK constraint of2.12.0-0
sets the default language version of every library in the package to 2.12, opting them all in to null safety. -
Open the package in your IDE.
You’re likely to see a lot of analysis errors. That’s OK. - Migrate the code of each Dart file,
using the analyzer to identify static errors.
Eliminate static errors by adding?
,!
,required
, andlate
, as needed.
See Unsound null safety for more help on migrating code by hand.
3. Analyze
Update your packages
(using pub get
in your IDE or on the command line).
Then use your IDE or the command line
to perform static analysis on your code:
$ dart pub get
$ dart analyze # or `flutter analyze`
4. Test
If your code passes analysis, run tests:
$ dart test # or `flutter test`
You might need to update tests that expect null values.
If you need to make large changes to your code, then you might need to remigrate it. If so, revert your code changes before using the migration tool again.
5. Publish
We encourage you to publish packages as prereleases as soon as you migrate:
- Set the SDK constraints to the tested beta version.
- Set the package version to indicate a breaking change.
SDK constraints
Set the lower SDK constraint to the beta version of 2.12
that you used to test the migration,
and the upper SDK constraint to <3.0.0
.
For example, if you’re using 2.12.0-29.10.beta,
then your constraints should look like this:
environment:
sdk: '>=2.12.0-29.10.beta <3.0.0'
With these constraints, packages that are published during null safety beta can still work with the next stable release of the Dart SDK.
Package version
Update the version of the package to indicate a breaking change:
-
If your package is already at 1.0.0 or greater, increase the major version. For example, if the previous version is
2.3.2
, the new version is3.0.0
. -
If your package hasn’t reached 1.0.0 yet, either increase the minor version or update the version to 1.0.0. For example, if the previous version is
0.3.2
, the new version is either0.4.0
or1.0.0
.
Because null safety has a stable API as of Dart 2.12.0-259.9.beta and later, you
can publish new stable versions of your package before null safety is
in a stable Dart SDK release.
The pub.dev site tags your new null safety version as a preview,
as the following screenshot shows for version
2.0.0
of package:args
:
Just after the first stable release of Dart 2.12, your package’s latest preview version automatically becomes the stable version on pub.dev.
Before you publish a stable null safety version of a package, we strongly recommend following these pubspec rules:
- Set the Dart lower SDK constraint to 2.12.0-259.9.beta.
- Use stable versions of all direct dependencies.
If these criteria are satisfied, you can ignore the following warning,
which appears when you run pub publish
:
Packages with an SDK constraint on a pre-release of the Dart SDK should themselves be published as a pre-release version.
You can maintain a stable release and preview release at the same time. For
example, if you have a stable release that’s 1.0.0
and a preview version that’s
2.0.0
, you can still publish new versions of the stable version (1.0.1
) and
preview version (2.0.1
).
Welcome to null safety
If you made it this far, you should have a fully migrated, null-safe Dart package. If all of the packages you depend on are migrated too, then your program is sound with respect to null-reference errors.
From all of the Dart team, thank you for migrating your code.