目录

迁移至空安全

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

  1. 等待 你依赖的 package 迁移完成。

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

  3. 静态分析 package 的代码。

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

  5. 如果你已经在 pub.dev 上发布了你的 package,可以将迁移完成的空安全版本以 预发布 版本进行 发布

如果你想预览迁移工具的体验,可以查看以下视频:

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

1. 等待迁移

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

Illustration of C/B/A sentence

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

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

切换至 Dart 2.19.6 版本

切换到 Dart SDK 的 2.19.6 稳定版,它包含在 Flutter 3.7.12 SDK 中。

执行下面代码查看是否使用了 Dart 2.19.6 版本:

$ dart --version
Dart SDK version: 2.19.6

检查所有依赖的迁移状态

通过以下命令检查你的 package 的迁移状态:

$ dart pub outdated --mode=null-safety

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

这是一个简单的 package 的输入示例。每个 package 的绿色对勾代表着对应版本已支持空安全:

Output of dart pub outdated

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

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

升级依赖

在迁移你的 package 的代码之前,请将它的依赖项升级至空安全版本。

  1. 运行命令 dart pub upgrade --null-safety 将依赖升级至支持空安全的最新版本。 注意: 该命令会更改你的 pubspec.yaml 文件。

  2. 运行命令 dart pub get

2. 迁移

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

针对迁移,你有两个选项可以选择:

使用迁移工具

迁移工具会带上一个非空安全的 package ,将它转换至空安全。你可以先在代码中添加 提示标记 来引导迁移工具的转换。

开始转换前,请做好如下的准备:

  • 使用 Dart SDK 的 2.19.6 版本。

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

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

$ dart migrate

如果你的 package 可以进行迁移,工具会输出类似以下的内容:

View the migration suggestions by visiting:

  http://127.0.0.1:60278/Users/you/project/mypkg.console-simple?authToken=Xfz0jvpyeMI%3D

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

Screenshot of migration tool

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

理解迁移的结果

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

举个例子,假设我们有如下的非空安全的代码:

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

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

改进迁移的结果

当分析结果推导了错误的可空性时,你可以添加临时的提示标记来改变建议的编辑:

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

    按下这些按钮,相应的标记会立刻添加到代码中,并且 无法撤销。如果你想删除标记,可以和平常一样使用代码编辑器删除它。

  • 就算迁移工具正在运行,你也可以使用编辑器添加提示标记。由于你的代码还未迁移到空安全,所以无法使用空安全的新特性。但是你可以进行与空安全无关的改动,例如重构。

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

下方的表格展示了可以使用的提示标记。

提示标记 对迁移工具的影响
expression /*!*/ 添加 `!` 至代码中,将 **表达式** 转换为其基础类型对应的不可空的类型。
type /*!*/ 将 **类型** 标记为非空。
/*?*/ 将前面的类型标记为可空。
/*late*/ 将变量声明标记为 `late`,表示其不会第一时间进行初始化。
/*late final*/ 将变量声明标记为 `late final`,表示其不会第一时间进行初始化,且初始化后不可改变。
/*required*/ 将参数标记为 `required`。

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

var zero = ints[0]/*!*/;

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

首次迁移 添加提示后的迁移
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];

只迁移部分文件

尽管我们希望你能一次性完成迁移工作,但对于大体量的应用或 package 而言并不是简单的事。如果你想只迁移部分文件,请将暂时不迁移的文件前方的绿色勾选框取消勾选。稍后应用迁移更改时,这些文件会加上 Dart 2.9 版本注释,其他内容保持不变。

更多有关渐进迁移空安全的内容,请阅读 非健全的空安全

请注意,从 Dart 3 开始,只支持完全迁移到空安全的应用和 package。

应用更改

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

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

手动迁移

如果你不想使用迁移工具,你也可以手动进行迁移。

我们推荐你 优先迁移最下层的库 —— 指的是没有导入其他 package 的库。接着迁移直接依赖了下层库的依赖库。最后再迁移依赖项最多的库。

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

手动对 package 进行迁移时,请参考以下步骤:

  1. 编辑 package 的 pubspec.yaml 文件,将最低 SDK 版本设置到至少为 2.12.0

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

    $ dart pub get
    

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

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

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

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

3. 分析

更新你的 package(在 IDE 或命令行工具中使用 dart pub get)后在 IDE 或命令行工具中对你的代码进行 静态分析

$ dart pub get
$ dart analyze     # or `flutter analyze`

4. 测试

如果你的代码通过了分析,接下来可以开始测试:

$ dart test       # or `flutter test`

你可能需要更新使用了空值作为预期用例的测试代码。

如果你需要对代码作出大量的更改,那么你可能需要重新对代码进行迁移。这时请先回滚代码更改,再运行迁移工具进行迁移。

5. 发布

我们希望你完成迁移后尽快将其发布,可以作为预览版:

更新 package 的版本

Package 的版本

你可以修改版本以表示该版本包含了破坏性的改动:

  • 如果你的 package 版本已经大于或等于 1.0.0,请提升主版本。例如,上一个版本为 2.3.2,那么新版本应该为 3.0.0

  • 如果你的 package 的版本还未高于 1.0.0,你可以 提升次版本,也可以 提升至 1.0.0。例如,上一个版本为 0.3.2,那么新版本可以是 0.4.01.0.0

检查你的 pubspec

在你发布稳定版本的空安全 package 前,我们强烈建议你遵循以下 pubspec 的规则:

  • 将 SDK 的最低限制设置为你测试过的最低稳定版本(至少是 2.12.0)。

  • 所有的直接依赖都使用稳定版本。

欢迎使用空安全

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. You should see output like this when running or compiling your code:

Compiling with sound null safety

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

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