Dart空安全?

1.空安全

最近打开很早之前的Flutter工程,发现大面积报错,无法运行。看了下报错信息,很多都是说变量不能为空,构造函数中命名参数不能为空。一番搜索之后,发现是Dart2.1.2和Flutter2.0版本开始推出一个重大更新:支持null safety了,这是一个与Swift中optional类似的空安全特性。它是编译阶段对属性、参数、返回值等可能为空null的类型的特殊处理。

2.作用

在空安全机制下:

  • 默认所有类型都是非空的
  • 除非你明确指出变量可以为空,否则它就不能是null;
  • 一旦非空变量为null,编译器即会报错;

例如,在没有空安全机制之前,我们写代码通常是这样:

1
2
3
4
5
6
// 没有 null safety 机制前:
bool isEmpty(String string) => string.length == 0;

main() {
isEmpty(null);
}

这段代码在编译时不会报错,但运行起来后就会在string.length处报错NoSuchMethodError,因为我们传递给isEmpty()方法的是个null,而null没有length方法。

在支持空安全之后,编译器会在调用isEmpty(null)时明确告诉你The argument type 'Null' can't be assigned to the parameter type 'String',即string参数声明为String类型,不能接收为null的参数。这样利用空安全机制,我们就能提前发现并改正传值为null的情况。

3.用法

3.1.?

与Swift一样,声明变量时,如果此变量可以为空,在类型后面加?号:

1
2
int a = 1;
int? b = null;

同理,参数或返回值可以为null时,也是在类型前加?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class NullableIdx{

int? idxText;

//工厂方法
static NullableIdx? setParam({int? idxText}){
if (idxText == null) {
return null; //可能返回空对象
}
NullableIdx obj = NullableIdx();
obj.idxText = idxText;
return obj;
}

//返回值可能为空
int? retIdx() {
if (idxText == null) {
return null;
}
return this.idxText;
}
}

void main(){
NullableIdx? obj = NullableIdx.setParam(idxText:null);
print('${obj?.retIdx()}'); //调用可空对象的方法时加?
}

上面的示例中还展示了另一个用法,即调用可空对象的方法obj?.retIdx()时,也需要对象后加?,与Swift中的可选链类似。

3.2.!

如果你确定一个变量、参数或返回值不为空,则使用!取其值,有点类似Swift中可选值的强制拆包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int? a = 1;

int? retIdx(String? idxText) {
if (idxText == null) {
return null;
}
return int.parse(idxText);
}

void main() {
print('${a!}'); //强制拆包
print('${retIdx('5')!}'); //强制拆包
print('${retIdx('xx')!}'); //闪退
}

!标记为的值如果为null,则会直接闪退。

3.3.late

在声明变量时,如果你确定它不能为空,但是又不想在声明时初始化它,你可以将其标注为late,告诉编译器:“稍后在使用这个变量前,我保证会先将其初始化”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class ExtData {
int? noSymptom;
late int allNum;

// 构造函数
ExtData({this.noSymptom}){
//1.构造函数内初始化late变量
allNum = 2; // 这里不初始化被标记为late的allNum时,运行时访问此变量会闪退
}
}

void main(){
ExtData data = ExtData();
//2.访问late变量之前初始化
data.allNum = 2;
print('${data.allNum}'); //访问late变量
}

注意,如果你不遵守自己的保证,在访问late变量之前没有将其初始化,会直接闪退。

3.4.required

构造函数中,如果命名参数参数不能为空,则你可通过两种方式显示的告诉编译器:

  • 在命名参数前加required关键字;
  • 给参数提供一个默认值;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ExtData {
int? noSymptom;
final int incrNoSymptom;
final int defaultInt;

// 构造函数
ExtData(this.noSymptom, //未命名参数
{required this.incrNoSymptom, //命名参数
this.defaultInt = 2}); //命名参数 给默认值
}

void main(){
ExtData data = ExtData(0,incrNoSymptom:1);
print('${data.incrNoSymptom},${data.defaultInt}');
}

注意:required只能加在函数命名参数的类型之前。

3.4.??

??是空或运算符,与Swift中的一样,表示如果变量为空,则给其一个默认值。

1
2
3
4
5
6
int? a = null;
int b = a ?? 1;

void main() {
print('$b'); //1
}

4.迁移到空安全

Dart2.1.2之前的代码迁移到2.1.2之后,默认使用null safety特性,编译器会报很多错误,很多都是提示你变量未初始化,或者构造函数中命名参数不能为空。所以,你需要将代码进行迁移。迁移最好是按照顺序进行:

  1. 将依赖库先迁移到空安全版本;
  2. 将自己的代码迁移到空安全版;
  3. 对自己的代码进行静态分析;
  4. 测试迁移后的代码已经生效;
  5. 如果是你自己的package,发布最新代码到pub.dev上;
4.1.迁移依赖库

1.首先确认本机Dart版本,至少要更新到2.1.2:

1
2
% dart --version
Dart SDK version: 2.17.6

2.CD到工程目录下,查看依赖库目前是否支持空安全。

输出结果中标记为绿色的即为支持null safety的版本。(博客里看不出来颜色,需要到终端里看~)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
% dart pub outdated --mode=null-safety
Showing dependencies that are currently not opted in to null-safety.
[✗] indicates versions without null safety support.
[✓] indicates versions opting in to null safety.

Package Name Current Upgradable Resolvable Latest

direct dependencies:
amap_location_fluttify ✗0.12.0 ✗0.12.0 ✓0.22.0-rc.0 ✓0.22.0-rc.0
charts_flutter ✗0.9.0 ✗0.9.0 ✓0.12.0 ✓0.12.0
cupertino_icons ✗0.1.3 ✗0.1.3 ✓1.0.5 ✓1.0.5
dio ✗3.0.9 ✗3.0.10 ✓4.0.6 ✓4.0.6
english_words ✗3.1.5 ✗3.1.5 ✓4.0.0 ✓4.0.0
http ✗0.11.3+17 ✗0.11.3+17 ✓0.13.5 ✓0.13.5
path_provider ✗1.6.9 ✗1.6.28 ✓2.0.11 ✓2.0.11
permission_handler ✗5.0.0+hotfix.6 ✗5.1.0+2 ✓10.0.0 ✓10.0.0
pull_to_refresh ✗1.5.8 ✗1.6.5 ✓2.0.0 ✓2.0.0
sqflite ✗1.3.0+1 ✗1.3.2+4 ✓2.0.3+1 ✓2.0.3+1
webview_flutter ✗0.3.22+1 ✗0.3.24 ✓3.0.4 ✓3.0.4

6 upgradable dependencies are locked (in pubspec.lock) to older versions.
To update these dependencies, use `dart pub upgrade`.

11 dependencies are constrained to versions that are older than a resolvable version.
To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --null-safety`.

3.更新各依赖库的版本号,以便支持null safety

命令执行完成后,项目里pubspec.yaml文件中各依赖库的版本号会被自动修改到支持null safety版。

1
% dart pub upgrade --null-safety

4.更新依赖库的代码:

1
% dart pub get

5.更新完成后,检查一下:

1
2
3
4
5
6
% dart pub outdated --mode=null-safety
Showing dependencies that are currently not opted in to null-safety.
[✗] indicates versions without null safety support.
[✓] indicates versions opting in to null safety.

All your dependencies declare support for null-safety.

这样,所有的依赖库就全部更新到null safety版本了,接下来就是迁移自己的代码了~

4.2.自动迁移代码

Dart提供了两种方式迁移你自己的代码:

  • 使用迁移工具;
  • 手动迁移;

1.推荐使用迁移工具,它会帮你检测需要迁移的代码,并提供迁移建议,你可以通过添加hint markers控制迁移工具的转换。

1
% dart migrate

2.一轮查询之后,Dart会生成一个http://127.0.0.1开头的链接:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
% dart migrate
Migrating /Users/davidli/Desktop/flutter_demo

See https://dart.dev/go/null-safety-migration for a migration guide.

Analyzing project...
[--------------------------------------------------------------------------------------------------------------------/]No analysis issues found.

Generating migration suggestions...
[---------------------------------------------------------------------------------------------------------------------]

Compiling instrumentation information...
[---------------------------------------------------------------------------------------------------------------------]

View the migration suggestions by visiting:

http://127.0.0.1:57135/Users/davidli/Desktop/flutter_demo?authToken=LV4lkFuY10w%3D

Use this interactive web view to review, improve, or apply the results.
When finished with the preview, hit ctrl-c to terminate this process.

If you make edits outside of the web view (in your IDE), use the 'Rerun from
sources' action.

3.用Chrome打开这个链接:

迁移工具-Chrome

蓝色标注的地方,就是迁移工具推荐的写法。点击其中某一个,右边Edit Details面板中会显示这么改的理由。

4.点击右上角的APPLY MIGRATION即可将代码迁移到空安全版本。同时,pubspec.yaml配置文件中Dart的版本号也会相应的被自动修改:

1
2
environment:
sdk: '>=2.12.0 <3.0.0'

5.做一次静态分析,检查代码是否有问题:

1
% dart analyze

6.最后测试一下代码:

1
% dart test
4.3.手动迁移代码

手动迁移代码最好从未依赖其他库的代码先入手,再迁移其他代码,大致顺序如下:

空安全-依赖迁移顺序

具体迁移步骤:

1.修改pubspec.yaml文件:

1
2
environment:
sdk: '>=2.12.0 <3.0.0'

2.更新依赖库,参考上面4.1.迁移依赖库章节。

3.打开你的工程,会出现大批编译错误,慢慢按照null safety的规则,修改相应的代码,如加?!required等。

4.执行静态分析:

1
% dart analyze

5.最后测试一下代码:

1
% dart test

至此,null safety的代码迁移就完成了。根据Dart官网的说法,整个工程和依赖库完全使用空安全后,包体会变小,运行速度也会得到提高。来给你的项目做个迁移吧~



Dart空安全?
https://davidlii.cn/2022/09/05/nullsafety.html
作者
Davidli
发布于
2022年9月5日
许可协议