Air safety of the Flutter series

I Into the air safety

What is empty security

Since Flutter 2, Flutter has enabled null security by default in the configuration. By incorporating null checking into the type system, these errors can be caught during development, so as to prevent crashes caused by the reproduction environment.

Benefits of introducing empty security

  • You can change the original control reference error at run time into an analysis error at edit time
  • Enhance the robustness of the program and effectively avoid the crash caused by Null
  • Follow the development trend of Dart and Flutter and leave no holes for subsequent iterations of the program

Minimum necessary knowledge of air safety

  • Air safety Baidu principle
  • Changes of Dart type system before and after introducing null security
  • Use of nullable (?) type
  • Use of late
  • Use of null assertion operator (!)

Principles of air safety

Dart's empty security support is based on the following three core principles

  • Cannot be null by default: unless you declare the displayed variable to be null, it must be a non null type;
  • Progressive migration: you are free to choose how much code will be migrated;
  • Safe and reliable: Dart's empty security is very reliable, which means that many optimizations are included during compilation
    If the type system infers that a variable is not empty, it will never be empty. But when you completely migrate the entire project and its dependencies to empty security, you will enjoy all the advantages of robustness -- fewer bugs, smaller binaries, and faster execution.

Changes of Dart type system before and after introducing null security

Before introducing null security, Dart's type system was as follows:

This means that before, all types can be Null, that is, Nul types are regarded as subclasses of all types.

After introducing empty security:

It can be seen that the biggest change is to separate the Null type, which means that Null is no longer a subtype of other types, so a type conversion error will be reported when passing a Null value to a variable of non Null type.

Tip: you will often see?.,!,, and in the Flutter or Dart projects that use empty security A large number of applications of late, what are they and how to use them? See the analysis below

Use of nullable (?) type

Can we pass? Variables or parameters that follow a type to indicate that they can accept nulls:

class CommonModel {
  String? firstName; //Nullable member variable
  int getNameLen(String? lastName /*Nullable parameter*/) {
    int firstLen = firstName?.length ?? 0;
    int lastLen = lastName?.length ?? 0;
    return firstLen + lastLen;
  }
}

For nullable variables or parameters, you need to use Dart's void avoidance operator Otherwise, a compilation error will be thrown.

When empty security is enabled in the program, the member variable of the class cannot be empty by default. Therefore, for a non empty member variable, its initialization method needs to be specified:

class CommonModel {
  List names=[];//Initialize on definition
  final List colors;//Initialize in constructor
  late List urls;//Delay initialization
  CommonModel(this.colors);
  ...

Use of late

If you cannot initialize at the time of definition and want to avoid using, Then delayed initialization can help you. Through the variable modified by late, developers can choose the initialization time, and can not use?. when using this variable.

 late List urls;//Delay initialization
  setUrls(List urls){
    this.urls=urls;
  }
  int getUrlLen(){
    return urls.length;
  }

Although delayed initialization can bring us some convenience in coding, it will lead to empty exceptions if it is not used properly. Therefore, when using it, we must ensure that the order of assignment and access is not reversed.

late use paradigm

Some variables initialized in the initState method of State in fluent are more suitable for delayed initialization. Because the initState method is executed first in the Widget life cycle, the initialized variables in it can not only ensure the convenience of use, but also prevent null exceptions after being modified by late

class _SpeakPageState extends State<SpeakPage>
    with SingleTickerProviderStateMixin {
  String speakTips = 'Long press to speak';
  String speakResult = '';
  late Animation<double> animation;
  late AnimationController controller;

  @override
  void initState() {
    controller = AnimationController(
        super.initState();
        vsync: this, duration: Duration(milliseconds: 1000));
    animation = CurvedAnimation(parent: controller, curve: Curves.easeIn)
      ..addStatusListener((status) {
        if (status == AnimationStatus.completed) {
          controller.reverse();
        } else if (status == AnimationStatus.dismissed) {
          controller.forward();
        }
      });
  }
  ...

Use of null assertion operator (!)

When we exclude the possibility that variables or parameters can be null, we can pass! To tell the compiler that this nullable variable or parameter is not nullable, which is particularly useful when we pass method parameters or nullable parameters to a non nullable input parameter:

 Widget get _listView {
    return ListView(
      children: <Widget>[
        _banner,
        Padding(
          padding: EdgeInsets.fromLTRB(7, 4, 7, 4),
          child: LocalNav(localNavList: localNavList),
        ),
        if (gridNavModel != null)
          Padding(
              padding: EdgeInsets.fromLTRB(7, 0, 7, 4),
              child: GridNav(gridNavModel: gridNavModel!)),
        Padding(
            padding: EdgeInsets.fromLTRB(7, 0, 7, 4),
            child: SubNav(subNavList: subNavList)),
        if (salesBoxModel != null)
          Padding(
              padding: EdgeInsets.fromLTRB(7, 0, 7, 4),
              child: SalesBox(salesBox: salesBoxModel!)),
      ],

The code is a list dynamically created according to whether the data of gridNavModel and salesBoxModel modules are empty. The null value assertion operator is used when ensuring that the variable is not empty!.

In addition,! Another common use:

bool isEmptyList(Object object) {
  if (object is! List) return false;
  return object.isEmpty;
}
Used here to indicate negation. The above code is equivalent to:

bool isEmptyList(Object object) {
  if (!(object is List)) return false;
  return object.isEmpty;
}

II How does Flutter short safety fit

1: Enable empty security

By default, blank security is enabled for the Flutter 2, so the project created through the Flutter 2 has been checked for blank security. In addition, you can view your version of the Flutter SDK through the following command:

flutter doctor

So, how to manually open and close the empty area safely?

environment:
  sdk: ">=2.12.0 <3.0.0" //sdk >=2.12. 0 indicates that the empty security check is enabled

Tip: once the project has enabled null security check, your code, including the third-party plug-ins that the project depends on, must support null security, otherwise it cannot be compiled normally.

If you want to turn off the empty security check, you can adjust the support range of the SDK to 2.12 0 or less, such as:

environment:
  sdk: ">=2.7.0 <3.0.0"

2. Air security adaptation skills of custom Widget

The air security adaptation of custom widgets can be divided into two cases:

  • Air security adaptation of Widget
  • Air safety adaptation of State
Air security adaptation of Widget

For a custom Widget, whether it is a control of the page or the whole page, some properties are usually defined for the Widget. During air safety adaptation, the attributes shall be classified as follows:

  • Nullable attributes: through the? Modify
  • Non nullable attribute: set the default value in the constructor or modify it through 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})
      ...

Tip: if @ required is used in the constructor, it needs to be changed to required.

Air safety adaptation of State

The air security adaptation of State is mainly classified according to whether its member variables can be empty:

  • Nullable variables:? Modify
  • Non nullable variables: the following two methods can be used for adaptation
    • Initialize on definition
    • Use late to modify as a delay variable
class _TravelPageState extends State<TravelPage> with TickerProviderStateMixin {
  late TabController _controller; //Delay initial
  List<TravelTab> tabs = []; //Initialize on definition
   ...
  @override
  void initState() {
    super.initState();
    _controller = TabController(length: 0, vsync: this);
    ...

3. Air safety adaptation skills of data Model

The air safety adaptation of data Model mainly includes the following two situations:

  • Model with named constructor
  • Model with named factory constructor

Model with named constructor

Before modification

class TravelItemModel {
  int totalCount;
  List<TravelItem> resultList;
  TravelItemModel.fromJson(Map<String, dynamic> json) {
    totalCount = json['totalCount'];
    if (json['resultList'] != null) {
      resultList = new List<TravelItem>();
      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;
    if (this.resultList != null) {
      data['resultList'] = this.resultList.map((v) => v.toJson()).toList();
    }
    return data;
  }
}

Before adaptation, first negotiate with the server. Those fields in the model can be empty, and those fields will be distributed. For this case, if the totalCount field is bound to be distributed and the resultList field is not guaranteed to be distributed, we can adapt it in this way:

After adaptation

class TravelItemModel {
  late int totalCount; 
  List<TravelItem>? resultList;

  //Named construction method
  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;
  }
}
  • For the fields that must be distributed, we modify them as delayed initialization fields through late to facilitate access
  • For fields that cannot be guaranteed to be distributed, we can use the? Modify it as an nullable variable

Model with named factory constructor

Before adaptation

class CommonModel {
  final String icon;
  final String title;
  final String url;
  final String statusBarColor;
  final bool hideAppBar;

  CommonModel(
      {this.icon, this.title, this.url, this.statusBarColor, this.hideAppBar});

  factory CommonModel.fromJson(Map<String, dynamic> json) {
    return CommonModel(
      icon: json['icon'],
      title: json['title'],
      url: json['url'],
      statusBarColor: json['statusBarColor'],
      hideAppBar: json['hideAppBar']
    );
  }
}

Models with named factory constructors usually need to have their own constructors. Constructors usually use optional parameters. Therefore, when adapting, first clarify which fields must not be empty and which fields can be empty. After confirmation, you can adapt as follows

After adaptation

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});
  //The named factory constructor must have a return value. Similar to static functions, member variables and methods cannot be accessed
  factory CommonModel.fromJson(Map<String, dynamic> json) {
    return CommonModel(
      icon: json['icon'],
      title: json['title'],
      url: json['url'],
      statusBarColor: json['statusBarColor'],
      hideAppBar: json['hideAppBar']
    );
  }
}
  • For nullable fields, click OK? Modify
  • For non nullable fields, you need to add the required modifier in front of the corresponding field in the construction method to indicate that this parameter is a required parameter

4. Single case air safety adaptation skills

Singleton is the most widely used design pattern in the development of fluent. How can singleton adapt to air safety?

///Cache management class
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);
  }

  T get<T>(String key) {
    return prefs?.get(key) ?? null;
  }
}

After adaptation

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;
  }
}

There are two main points for core adaptation:

  • Because it is a singleton of lazy mode, the singleton instance is set to nullable
  • Because singletons will be created when null is present in getInstance, they will be converted to non null when instance is returned

5. Air safety adaptation of third-party plug-ins

At present, the mainstream plug-ins on Dart's official plug-in platform have been supported by empty security one after another. If your project has enabled empty security, all plug-ins used must also support empty security, otherwise they will fail to compile:

    Error: Cannot run with sound null safety, because the following dependencies
    don't support null safety:

     - package:flutter_splash_screen

After encountering this problem, you can go to Dart's official plug-in platform to view this flitter_ splash_ Whether the screen plug-in has a version that supports empty security. If the plug-in supports empty security, the plug-in platform will mark it safe

If you use a plug-in that does not support null security, and you have to use this plug-in, you can turn off null security check in the way described above.

How can my plug-in adapt to empty security?

There are three key steps:

  • Open empty security code adaptation:
  • Compile and perform air safety adaptation for the compiled error messages
  • Release: release the adapted code to the plug-in market

Keywords: Flutter

Added by andrew_U on Thu, 23 Dec 2021 00:10:04 +0200