1.空安全 最近打开很早之前的Flutter工程,发现大面积报错,无法运行。看了下报错信息,很多都是说变量不能为空,构造函数中命名参数不能为空。一番搜索之后,发现是Dart2.1.2和Flutter2.0版本开始推出一个重大更新:支持null safety
了,这是一个与Swift中optional
类似的空安全
特性。它是编译阶段对属性、参数、返回值等可能为空null
的类型的特殊处理。
2.作用 在空安全机制下:
默认所有类型都是非空的 ;
除非你明确指出变量可以为空,否则它就不能是null;
一旦非空变量为null,编译器即会报错;
例如,在没有空安全机制之前,我们写代码通常是这样:
1 2 3 4 5 6 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}){ allNum = 2 ; } }void main(){ ExtData data = ExtData(); data.allNum = 2 ; print ('${data.allNum} ' ); }
注意,如果你不遵守自己的保证,在访问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' ); }
4.迁移到空安全 Dart2.1.2之前的代码迁移到2.1.2之后,默认使用null safety
特性,编译器会报很多错误,很多都是提示你变量未初始化,或者构造函数中命名参数不能为空。所以,你需要将代码进行迁移。迁移最好是按照顺序进行:
将依赖库先迁移到空安全版本;
将自己的代码迁移到空安全版;
对自己的代码进行静态分析;
测试迁移后的代码已经生效;
如果是你自己的package,发布最新代码到pub.dev上;
4.1.迁移依赖库 1.首先确认本机Dart版本,至少要更新到2.1.2:
1 2 % dart 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
版。
4.更新依赖库的代码:
5.更新完成后,检查一下:
1 2 3 4 5 6 % dart pub outdated 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
控制迁移工具的转换。
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... [ 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%3 DUse 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打开这个链接:
蓝色标注的地方,就是迁移工具推荐的写法。点击其中某一个,右边Edit Details
面板中会显示这么改的理由。
4.点击右上角的APPLY MIGRATION
即可将代码迁移到空安全版本。同时,pubspec.yaml
配置文件中Dart的版本号也会相应的被自动修改:
1 2 environment : sdk : '>=2 .12 .0 <3 .0 .0 '
5.做一次静态分析,检查代码是否有问题:
6.最后测试一下代码:
4.3.手动迁移代码 手动迁移代码最好从未依赖其他库的代码先入手,再迁移其他代码,大致顺序如下:
具体迁移步骤:
1.修改pubspec.yaml
文件:
1 2 environment : sdk : '>=2 .12 .0 <3 .0 .0 '
2.更新依赖库,参考上面4.1.迁移依赖库
章节。
3.打开你的工程,会出现大批编译错误,慢慢按照null safety
的规则,修改相应的代码,如加?
、!
、required
等。
4.执行静态分析:
5.最后测试一下代码:
至此,null safety
的代码迁移就完成了。根据Dart官网的说法,整个工程和依赖库完全使用空安全
后,包体会变小,运行速度也会得到提高。来给你的项目做个迁移吧~