空安全:
Dart从2.12版本开始支持了空安全,通过空安全开发人员可以有效避免null错误崩溃。空安全性可以说是Dart语言的重要补充,它通过区分可空类型和非可空类型进一步增强了类型系统。
引入空安全的好处
- 可以将原本运行时的空值引用错误将变为编辑时的分析错误;
- 增强程序的健壮性,有效避免由Null而导致的崩溃;
- 跟随Dart和Flutter的发展趋势,为程序的后续迭代不留坑;
空安全最小必备知识:
- 空安全的原则
- 引入空安全前后Dart类型系统的变化
- 可空(?)类型的使用
- 延迟初始化(late)的使用
- 空值断言操作符(!)的使用
/// 在使用了空安全的Flutter或Dart项目中你会经常看到?.、!、late的大量应用
class CommonModel {
String? firstName; //可空的成员变量
int getNameLen(String? lastName /*可空的参数*/) {
int firstLen = firstName?.length ?? 0;
int lastLen = lastName?.length ?? 0;
return firstLen + lastLen;
}
}
// !还有一个常见的用处:
bool isEmptyList(Object object) {
// if (!(object is List)) return false;
if (object is! List) return false;
return object.isEmpty;
}
当程序启用空安全后,类的成员变量默认是不可空的,所以对于一个非空的成员变量需要指定其初始化方式:
class CommonModel {
List names=[];//定义时初始化
final List colors;//在构造方法中初始化
late List urls;//延时初始化
CommonModel(this.colors);
}
Widget的空安全适配
- 可空的属性:通过?进行修饰
- 不可空的属性:在构造函数中设置默认值或者通过required进行修饰
class WebView extends StatefulWidget {
String? url;
final String? statusBarColor;
final String? title;
final bool? hideAppBar;
final bool backForbid;
WebView(
{this.url,
this.statusBarColor,
this.title,
this.hideAppBar,
this.backForbid = false})
}
State的空安全适配
State的空安全适配主要是根据它的成员变量是否可空进行分类:
- 可空的变量:通过?进行修饰
- 不可空的变量:可采用以下两种方式进行适配
定义时初始化
使用late修饰为延时变量
class _TravelPageState extends State<TravelPage> with TickerProviderStateMixin {
late TabController _controller; //延时初始
List<TravelTab> tabs = []; //定义时初始化
...
@override
void initState() {
super.initState();
_controller = TabController(length: 0, vsync: this);
...
}}
数据模型Model空安全适配。
数据模型(Model)空安全适配主要以下两种情况:
- 含有命令构造函数的模型
- 含有命名工厂构造函数的模型
///旅拍页模型
class TravelItemModel {
late int totalCount;
List<TravelItem>? resultList;
//命名构造方法
TravelItemModel.fromJson(Map<String, dynamic> json) {
totalCount = json['totalCount'];
if (json['resultList'] != null) {
resultList = new List<TravelItem>.empty(growable: true);
json['resultList'].forEach((v) {
resultList!.add(new TravelItem.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['totalCount'] = this.totalCount;
data['resultList'] = this.resultList!.map((v) => v.toJson()).toList();
return data;
}
}
- 对于一定会下发的字段我们通过late来修饰为延迟初始化的字段以方便访问
- 对于不能保证一定会下发的字段,我们通过?将其修饰为可空的变量
含有命名工厂构造函数的模型
class CommonModel {
final String? icon;
final String? title;
final String url;
final String? statusBarColor;
final bool? hideAppBar;
CommonModel(
{this.icon, this.title, required this.url,
this.statusBarColor, this.hideAppBar});
//命名工厂构造函数必须要有返回值,类似static 函数无法访问成员变量和方法
factory CommonModel.fromJson(Map<String, dynamic> json) {
return CommonModel(
icon: json['icon'],
title: json['title'],
url: json['url'],
statusBarColor: json['statusBarColor'],
hideAppBar: json['hideAppBar']
);
}
}
- 对于可空的字段通过?进行修饰
- 对于不可空的字段,需要在构造方法中在对应的字段前面添加required修饰符来表示这个参数是必传参数
单例的空安全适配。
class HiCache {
SharedPreferences? prefs;
static HiCache? _instance;
HiCache._() {
init();
}
HiCache._pre(SharedPreferences prefs) {
this.prefs = prefs;
}
static Future<HiCache> preInit() async {
if (_instance == null) {
var prefs = await SharedPreferences.getInstance();
_instance = HiCache._pre(prefs);
}
return _instance!;
}
static HiCache getInstance() {
if (_instance == null) {
_instance = HiCache._();
}
return _instance!;
}
void init() async {
if (prefs == null) {
prefs = await SharedPreferences.getInstance();
}
}
setString(String key, String value) {
prefs?.setString(key, value);
}
setDouble(String key, double value) {
prefs?.setDouble(key, value);
}
setInt(String key, int value) {
prefs?.setInt(key, value);
}
setBool(String key, bool value) {
prefs?.setBool(key, value);
}
setStringList(String key, List<String> value) {
prefs?.setStringList(key, value);
}
remove(String key) {
prefs?.remove(key);
}
T? get<T>(String key) {
var result = prefs?.get(key);
if (result != null) {
return result as T;
}
return null;
}
}
核心适配的地方主要有两点:
- 因为是懒汉模式的单例,所以单例instance设置成可空
- getInstance中因为会有null时创建单例,所以返回instance时将其转换成非空
空安全适配常见问题。
type 'Null' is not a subtype of type 'String'.
导致此问题的主要原因将一个null值传递给了一个不能为null的参数,常见在使用model时.
解决方案
- 如果控制台有输出具体报错行数,则可以跳转到具体的代码行进行解决
- 如果控制台没有出入具体的代码行数,则可以通过debug方式在代码的catch或catchError节点下打个断点,然后当查看catch中具体的报错信息