目录

Contents

迁移至空安全

本文将介绍如何将您的代码迁移至 空安全。以下是对您的软件包逐个迁移的基本步骤:

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:

  1. 等待 您依赖的软件包迁移完成。

    Wait for the packages that you depend on to migrate.

  2. 迁移 您的包的代码,最好使用交互式的迁移工具。

    Migrate your package’s code, preferably using the interactive migration tool.

  3. 静态分析 包的代码。

    Statically analyze your package’s code.

  4. 测试 您的代码,确保可用。

    Test to make sure your changes work.

  5. 如果您已经在 pub.flutter-io.cn 发布了您的包,可以将迁移完成的空安全版本以 预发布 版本进行 发布

    If the package is already on pub.dev, publish the null-safe version as a prerelease version.

可交互的迁移工具让您可以简化迁移至空安全的过程。

Screenshot of migration tool The interactive migration tool can simplify migration to null safety.

1. 等待迁移

1. Wait to migrate

我们强烈建议您按顺序迁移代码,先迁移依赖关系中的处于最末端的依赖。例如,如果 C 依赖了 B,B 依赖了 A,那么应该按照 A -> B -> C 的顺序进行迁移。

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.

Illustration of C/B/A sentence

虽然您在您的所有依赖迁移完成前就 可以 进行迁移,但在它们迁移完成后,您可能需要再对您的代码进行调整。例如,如果您推测一个函数可以接受一个可空的参数,但依赖的包迁移后变为了非空,在传递可空的参数时便会出现编译错误。

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.

该节会讲述如何在空安全模式下,使用 dart pub outdated 检查并更新您的依赖。如果您的代码应用了 版本管理,您可以随时回滚所有的改动。

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.

切换至 2.12 beta 版本

Switch to a 2.12 beta release

切换至最新的 beta 版本 的 Dart SDK 或 Flutter SDK。您可以根据您的使用情况,参考以下的方式获取 SDK:

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 SDK,请切换至 beta 频道:

    $ flutter channel beta
    $ flutter upgrade
    
  • Otherwise, download a beta release from the Dart SDK archive.

  • 否则,请从 Dart SDK 归档 下载最新的 beta 版本。

检查所有依赖的迁移状态

Check dependency status

通过以下命令检查您的依赖包的迁移状态:

Get the migration state of your package’s dependencies, using the following command:

$ dart pub outdated --mode=null-safety

如果您看到所有依赖都已支持空安全,就意味着您可以开始迁移了。否则请使用 Resolvable 列内列举的已迁移至空安全的版本。

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:

Output of dart pub outdated

上面的输出说明了所有依赖的包都有可使用的已支持空安全的预发布版本。

The output shows that all of the package’s dependencies have resolvable prereleases that support null safety.

如果您的软件包的依赖中,有一些 尚未 支持空安全,我们推荐您联系对应依赖的作者。您可以在 pub.flutter-io.cn 对应依赖包的页面,找到作者的联系信息。

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:

  1. 更新 pubspec.yaml,使用已发布空安全版本的依赖包(在 Resolvable 列中列出的)。省略后缀版本号 .x,使之能够被更灵活地解析,并且 不要 更新 SDK 最低版本限制。下面是一个 pubspec.yaml 文件的示例:

    Update pubspec.yaml to use null-safe releases (as listed in the Resolvable column) of its dependencies. Omit .x suffixes to make version solving more flexible, and don’t update the SDK minimum constraint. For example, the pubspec.yaml file might look like this:

    ...
    environment:
      sdk: '>=2.8.1 <3.0.0'
    
    dependencies:
      path: ^1.8.0-nullsafety
      process: ^4.0.0-nullsafety
    
    dev_dependencies:
      pedantic: ^1.10.0-nullsafety
    
  2. 运行 dart pub upgrade

    Run dart pub upgrade.

2. 迁移

2. Migrate

您的代码里大部分需要更改的代码,都是可以轻易推导的。例如,如果一个变量可以为空,它的类型需要 ? 后缀。一个不可以为空的命名参数,需要使用 required 标记

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:

使用迁移工具

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:

  • 使用 2.12 beta 版本的 Dart SDK。

    Use the latest 2.12 beta release of the Dart SDK.

  • 运行 dart pub outdated --mode=null-safety 以确保所有依赖为最新且空安全。

    Use dart pub outdated --mode=null-safety to make sure that all dependencies are null safe and up-to-date.

在包含 pubspec.yaml 的目录下,执行 dart migrate 命令,启动迁移工具。

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

使用 Chrome 浏览器访问 URL,您可以看到一个交互式的界面,引导您进行迁移:

Visit that URL in a Chrome browser to see an interactive UI where you can guide the migration process:

Screenshot of migration tool

您可以在工具中看到其推断的所有变量和类型注解。例如,在上面的截图中,工具推断第一行的 ints 列表元素可能为空,所以应该变为 int?(先前为 int)。

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

若要了解每个变化(或者未变化)的原因,点击 Proposed Edits 窗口中的行数,原因会出现在 Edit Details 窗口中。

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];

点击 line 3 链接,您可以看到迁移工具添加 ! 的原因。而因为您知道 zero 不会为空,所以您可以改进迁移结果。

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:

  • 在迁移工具的 Edit Details 窗格中,您可以通过 Add /*?*/ hintAdd /*!*/ hint 按钮来添加提示标记。

    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.

    当您完成编辑后,点击 Rerun from sources 进行更改。

    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*/ 将变量声明标记为 `late`,表示其不会第一时间进行初始化。Marks the variable declaration as `late`, indicating that it has late initialization.
/*late final*/ 将变量声明标记为 `late final`,表示其不会第一时间进行初始化,且初始化后不可改变。Marks the variable declaration as `late final`, indicating that it has late, one-time initialization.
/*required*/ 将参数标记为 `required`。Marks the parameter as `required`.

一个提示也可能产生蝴蝶效应,影响其他的代码。在先前的例子中,如果在 zero 被赋值的位置(第二行)添加一个 /*!*/ 标记,迁移工具就会推断 zero 的类型是 int 而非 int?。这就会影响到直接或间接使用了 zero 的代码。

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]/*!*/;

通过添加了以上的提示,迁移工具将调整建议的更改,如下面的代码所示。第三行的 zero 后面不再有 !,第四行的 zeroOne 也被推断为 int 列表而不是 int?

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
var ints = const <int?>[0, null];
var zero = ints[0];
var one = zero! + 1;
var zeroOne = <int?>[zero, one];
var ints = const <int?>[0, null];
var zero = ints[0]/*!*/;
var one = zero + 1;
var zeroOne = <int>[zero, one];

应用更改

Applying changes

当您觉得迁移工具提示的更改部分可以应用了,点击 Apply migration。迁移工具会删除所有的提示标记,保存迁移后的代码。同时,迁移工具也会更改 pubspec 的 SDK 限制,将包迁移至空安全。

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.

下一步就是对代码进行 静态分析。如果一切正常,下一步就是 测试您的代码。最后,如果您已经将包发布至 pub.flutter-io.cn, 发布空安全的预览版本

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.

举个例子,假设您的 lib/src/util.dart 导入了其他(空安全)的软件包和核心库,但它没有包含任何 import '<本地路径>' 的引用。那么您应当优先考虑迁移 util.dart,然后迁移依赖了 util.dart 的文件。如果有一些循环引用的库(例如 A 引用了 B,B 引用了 C,C 引用了 A),建议同时对它们进行迁移。

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:

  1. 编辑软件包的 pubspec.yaml 文件,将最低 SDK 版本设置到 2.12.0-0

    Edit the package’s pubspec.yaml file, setting the minimum SDK constraint to 2.12.0-0:

    environment:
      sdk: '>=2.12.0-0 <3.0.0'
    
  2. 重新生成 软件包的配置文件

    Regenerate the package configuration file:

    $ dart pub get
    

    在版本最低是 2.12.0-0 的 SDK 上运行 dart pub get 时,会将每个包的默认 SDK 版本设定为 2.12,并且默认它们已经迁移至空安全。

    Running dart pub get with a lower SDK constraint of 2.12.0-0 sets the default language version of every library in the package to 2.12, opting them all in to null safety.

  3. 在您的 IDE 上打开软件包。
    您也许会看到很多错误,没关系,让我们继续。

    Open the package in your IDE.
    You’re likely to see a lot of analysis errors. That’s OK.

  4. 利用分析器来辨析静态错误,逐个迁移 Dart 文件。
    按需添加 ?!required 以及 late 来消除静态错误。

    Migrate the code of each Dart file, using the analyzer to identify static errors.
    Eliminate static errors by adding ?, !, required, and late, as needed.

想获得更多手动迁移的帮助,请前往 非健全的空安全

See Unsound null safety for more help on migrating code by hand.

3. 分析

3. Analyze

更新您的软件包(在 IDE 或命令行工具中使用 pub get)后在 IDE 或命令行工具中对您的代码进行 静态分析

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. 测试

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. 发布

5. Publish

我们希望您完成迁移后尽快将其发布为预览版:

We encourage you to publish packages as prereleases as soon as you migrate:

SDK 限制

SDK constraints

将 SDK 限制的下界设置为您测试迁移时的 2.12 beta 版本,上界设置为 <3.0.0。例如,如果您正在使用 2.12.0-29.10.beta,那么您的限制看起来应该是这样的:

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'

在空安全测试期间以这种形式进行发布的包,能在下一个 Dart SDK 稳定版中继续使用。

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

为版本添加 nullsafety 后缀,表示该版本包含了破坏性的改动。

Update the version of the package to indicate a breaking change and include a nullsafety suffix:

  • 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 is 3.0.0-nullsafety.0.

  • 如果您的软件包版本已经大于或等于 1.0.0,请提升主版本。例如,上一个版本为 2.3.2,那么新版本应该为 5.0.0-nullsafety.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 one of the following:
    • 0.4.0-nullsafety.0
    • 1.0.0-nullsafety.0
  • 如果您的软件包的版本还未高于 1.0.0,您可以 提升次版本,也可以 提升至 1.0.0。例如,上一个版本为 0.3.2,那么新版本可以是:
    • 0.4.0-nullsafety.0
    • 1.0.0-nullsafety.0

如果需要更新已发布的空安全预览版本,请提升后缀版本。例如,如果首个空安全版本为 3.0.0-nullsafety.0,那么下一个版本应该为 3.0.0-nullsafety.1

For subsequent updates to the null-safe prerelease of the package, increment the prerelease suffix. For example, if the first null-safe version is 3.0.0-nullsafety.0, then the next one is 3.0.0-nullsafety.1.

当 Dart SDK 中的空安全为稳定版时,我们希望您将空安全软件包发布为稳定版本。

Once null safety is available in a stable release of the Dart SDK, we encourage you to publish a stable version of your null-safe package.

欢迎使用空安全

Welcome to null safety

如果您走到了这一步,您应该已经完全将您的 Dart 软件包迁移至空安全了。当您的所有依赖也都完成了迁移,那么您的程序就是健全的,同时可以正确处理空引用的错误。

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.

谨代表 Dart 团队的所有成员,感谢您 迁移您的代码。

From all of the Dart team, thank you for migrating your code.