前言
Flutter 从 2.0 版本开始支持空安全(Null Safety)。dart 版本为:
dart
environment:
sdk: ">=2.12.0 < 3.0.0"
升级到空安全后,由于语法的变动,基本上整个工程,代码都爆红,这对项目来说简直是灾难性的打击,不升级的话只是缓兵之计,因为随着时间的推移,flutter将不再维护非空安全的版本,同时一些三方库也将无法使用,因此空安全升级变成了一个不得不做的事情,项目越复杂需要的时间也就越持久,考虑到对项目的稳定性,和开发周期。选择一个合适自己项目的迁移方案非常重要。下面自己会讲下自己对空安全迁移的理解和实施方案,目前成功迁移几个项目,还是比较有经验的。
语法变动
空安全升级后dart语法是存在很大的变化的,但是理念很简单即:万物皆可为空 ,和以往不同,现在任意创建一个对象都需要判断对象是否为空,这样在使用的时候,便明确知道这个对象是否可能为空,可以避免很多空指针的情况。
Dart 空安全的关键语法
1.不可空类型
在启用空安全的 Dart 中,默认情况下所有类型都是不可空的。这意味着一个变量声明后,不能为 null。例如:
dart
int x = 42; // x 不能为 null
如果尝试将 null 赋值给不可空类型,编译器将会报错。
2.可空类型
如果一个变量可以是 null,则必须在类型后加上 ?。例如:
dart
int? y = null; // y 可以是整数或 null
这种语法清晰地表明 y 是可空的,开发者在使用时必须考虑它可能为 null 的情况。
3.非空断言操作符 !
当你确定一个可空变量不为 null 时,可以使用 ! 操作符进行非空断言。例如:
dart
int? a = 5;
int b = a!; // 断言 a 不为 null,安全地将其赋值给 b
使用 ! 操作符时要小心,因为如果断言错误(即变量实际上为 null),程序会抛出异常。因此对于不确定的变量尽量不要强制!
4.空合并操作符 ??
空合并操作符提供了一种为可空变量指定默认值的便捷方式。例如:
dart
String? name;
String displayName = name ?? 'Guest'; // 如果 name 为 null,则使用 'Guest'
这种操作符在开发中是非常常用的,我们经常会通过??来给一些可为空的对象创建兜底值
5.late 变量
有时候我们明确知道这个对象一定是不会为null的但是再创建的时候拿不到值,便可以通过late 来延迟初始化这个对象
dart
late String description;
void initialize() {
description = 'Dart is fun!';
}
使用 late 声明的变量在首次使用前必须被初始化,否则将导致运行时错误。
空安全迁移
Flutter 官方提供了空安全迁移方案:使用dart migrate 工具可以借助该工具
在开始迁移之前,请确保做好以下准备工作:
升级 Dart SDK:确保你使用的 Dart SDK 版本是 2.12 或更高版本。可以通过 dart --version 检查当前的 Dart 版本。
更新依赖:确保项目中的所有依赖包都支持空安全。可以使用以下命令查看哪些包需要更新:
dart
dart pub outdated --mode=null-safety
可以根据输出的日志将不支持空安全的版本升级或者替换
以上都完成后,便可以使用 dart migrate 来进行代码的替换了
dart migrate工具会为你构建迁移后的代码建议,并启动一个交互式的迁移网页界面。
dart
View the migration suggestions by visiting:
http://127.0.0.1:60278/Users/you/project/mypkg.console-simple?authToken=Xfz0jvpyeMI%3D
以上便可以通过dart migrate进行简单的迁移
手动迁移
真实的开发场景哪有这么理想,因此使用dart migrate对很多大型项目并不是很使用,并且使用dart migrate会大量的添加** !** 导致项目一堆异常,无法真正的运行起来。
另外也没有那么多时间,让项目进度停下来做空安全迁移。
因为我们做的方案是:创建一个支持空安全语法的module
dart
//需要安卓和iOS模块
flutter create --template=plugin --org com.xxxx.xxx --platforms android --platforms ios --project-name aaa bbb
dart
flutter create --template=plugin --org com.xxxx.xxxxx --project-name aaaa bbbb
将创建好的工程以module的形式,依赖到主项目中,此方法不会打破项目的连续性,可以边迁移,边做需求开发。需要注意的是:
需要将主项目(非空安全)和子module(空安全)以相对路径的方式进行依赖因此需要修改pubspec.yaml 中的依赖方式
UT和覆盖率需要以下面的方式进行run
dart
flutter test --no-sound-null-safety
在整体空安全都迁移完成后,再将项目移会去,这样边可以,一部分同时进行正常的需求开发,一部分同时进行空安全整改,最大化的提高工作效率。
当然在迁移过程中会涉及到import的问题,这里自己写了脚本,用于批量修改导包
dart
#!/bin/sh
echo "修改工程导包"
# 修改对应的一级文件夹
CHANGEFILE="xxxx"
# 修改以package开头的导包
package_name="package:xxxxx\/$CHANGEFILE\/"
new_string=""
old_string="\.\.\/"
search_string="\.\.\/$CHANGEFILE"
old_import="$CHANGEFILE\/"
# 从主项目迁移到空安全路径修改导包路径名称
new_import="package:mobile_cn_null_safety\/$CHANGEFILE\/"
find . -name "*.dart"|while read fname; do
echo "The string is found on line(s): $fname"
#package 开头导包
if grep -q "$package_name" "$fname"; then
# 使用grep查找字符串所在行
package_line_number=$(grep -n "${package_name}" "$fname" | cut -d: -f1)
# 遍历行号
for each1 in $package_line_number
do
if [ -n "$each1" ];then
sed -i '' "${each1}s/${package_name}/${new_import}/g" "$fname"
fi
done
fi
# 相对路径导包
if grep -q "$search_string" "$fname"; then
# 使用grep查找字符串所在行
line_number=$(grep -n "${search_string}" "$fname" | cut -d: -f1)
for each in $line_number
do
if [ -n "$each" ];then
sed -i '' "${each}s/${old_string}//g" "$fname"
sed -i '' "${each}s/${old_import}/${new_import}/g" "$fname"
fi
done
fi
done
以上差不多便是全部内容。