Dart空安全

空安全:

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中具体的报错信息
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Null Safety in Dart --- Introduction (Dart空安全介绍) 历史 2020年...
    spt_genius阅读 9,699评论 0 5
  • 一、前言 在dart 2.12版本之前,任何一种类型都可以为null,所有的类型默认都是null,这样也就意味着我...
    yuyangkk阅读 6,800评论 1 3
  • 在11月20号,官方发布了健全的空安全Beta版本。空安全作为近期最具生产力的功能,可以帮助大家避免空异常问题。 ...
    两三行代码阅读 5,359评论 0 1
  • 一、走进空安全(空安全最小必备知识) 从Flutter 2开始,Flutter便在配置中默认启用了空安全,通过将空...
    AlanGe阅读 6,466评论 0 1
  • Why null safety? 空安全是一个非常具有生产力的功能,能够帮助你避免空异常。除此之外它也能有一定程度...
    两三行代码阅读 4,101评论 1 1