Dart Organize Notes | Dart Reference Manual

Dart SDK installation

If you are developing with Flutter, the Flutter SDK comes with it, so you don't need to install the Dart SDK separately.

If you need to debug and run Dart code independently, you need to install the Dart SDK independently.

Official documentation: https://dart.dev/get-dart

Dart development tools:

  • There are many development tools for Dart: IntelliJ IDEA, WebStorm, Atom, Vscode, Android Studio, etc.
  • Run Dart online without using IDE: https://dartpad.cn

If Dart is configured in Vscode:

  1. Find the vscode plugin to install dart

  2. Find the vscode plugin to install code runner, Code Runner can run our files

Base

Dart is a strongly typed language , any variable has a definite type .

Official Documentation: An Overview of the Dart Programming Language

Variables and Naming

Automatic type inference:

var name = 'lisa'; // name 变量的类型被推断为 String

In Dart, after vardeclaring a variable, Dart will infer its type based on the type of the first assigned data at compile time, and its type has been determined after compilation. For example, the above name has been determined to be of String type, and other types cannot be assigned to it, otherwise an error will be reported. (This behavior is consistent with kotlin)

var name = 'lisa';

name = 123;  // 报错
name = '张三'; // 不报错

Declare variables with definite types whenever possible

String name = '张三';

You can use dynamicdynamic typing if you don't know the type

dynamic name ="张三";
name = 666;

Dart naming rules:

  1. Variable names must consist of numbers, letters, underscores, and dollar signs ($).
  2. Note: Identifiers cannot begin with a number
  3. Identifiers cannot be reserved words and keywords.
  4. Variable names are case sensitive eg: age and Age are different variables. In actual use, it is also recommended not to use a word case to distinguish two variables.
  5. Identifiers (variable names) must see the meaning of the name: it is recommended to use nouns for variable names, and verbs for method names

List of Dart language keywords:
insert image description here

In Dart, uninitialized variables default to null. The default value is true even if the variable is a number , because everything is an objectnull in Dart , and numbers are no exception.

int? num;
print(num); //null

dart entry function:

// void 可以省略
void main(){
    
    
 	print('你好dart');
}

constant

Variables that are never modified during use can use finalor const, not varor other types. The difference between the two is: a const variable is a compile-time constant, and a final variable is initialized when it is first used . For used finalor constmodified variables, the variable type can be omitted.

constYou have to assign a value at the beginning, finaland you can assign it once without assigning a value; finalnot only consthas the characteristics of a compile-time constant, the most important thing is that it is a runtime constant, and it finalis lazy initialization, that is, it is initialized before it is used for the first time at runtime .

  // 使用final定义变量。
  final String name = '李四';
  final int age = 24;
  
  // 使用const定义变量。
  const String gender = '男';
  const int height = 165;
final name = 'Bob'; // Without a type annotation 
const bar = 1000000;  

The same constconstant will not be created repeatedly in memory, but finalwill:

  final finalList1 = [1, 2];
  final finalList2 = [1, 2];
  print(identical(finalList1, finalList2)); // false identical 用于检查两个引用是否指向同一个对象
  const constList1 = [1, 2];
  const constList2 = [1, 2];
  print(identical(constList1, constList2)); // true

built-in type

The Dart language supports the following built-in types:

  • Number : intdouble
  • String:String
  • Boolean:bool
  • List (also known as Array)
  • Map
  • Set
  • Rune (for representing Unicode characters in strings)
  • Symbol

These types can all be initialized as literals.

Because all variables in Dart are ultimately an object (an instance of a class), variables can be initialized using constructor functions. Some built-in types have their own constructors. For example, Map()construct a map variable by .

string

Several ways of string definition

var str1='this is str1';
var str2="this is str2"; 

String str1='this is str1';
String str2="this is str2"; 

String str1='''this is str1
  this is str1
  this is str1
  '''; 

String str1="""
    this is str1
    this is str1
    this is str1
    """; 

concatenation of strings

  String str1='你好';
  String str2='Dart';
  print("$str1 $str2");
  print(str1 + str2);  
  print(str1 +" "+ str2);

String to other types are through parsethe method:

  String str = '123';
  var myNum = int.parse(str);
  print(myNum is int);

  String str2 = '123.1';
  var myNum2 = double.parse(str2);
  print(myNum2 is double);

  String price = '12';
  var myNum3 = double.parse(price);
  print(myNum3);
  print(myNum3 is double);

Other types of Stringtransfer toStringmethods

  print(myNum3.toString());  
  int? bbb;
  print(bbb.toString()); //正常 其他类型的null可以转成null字符串
  String? ccc;
  print(double.parse(ccc));//报错,null类型String不能转为其他类型

string split

var list = str.split('-'); // 字符串分割转化成List

Runes

  // 表示符文的意思,用于在字符串中表示Unicode字符。
  // 使用String.fromCharCodes显示字符图形。如果非4个数值,需要把编码值放到大括号中。
  Runes runes = Runes('\u{1f605} \u6211');
  var str1 = String.fromCharCodes(runes);  
  print(str1);

List (array/collection)

The objects in the List can be different, several ways to define the List

  // 1、第一种定义List的方式
  var l1 = ["张三",20,true];
  print(l1);  //[张三, 20, true]
  print(l1[0]); //张三
  print(l1[1]); //20
  print(l1.length);  //3
  // 2、第二种定义List的方式 指定类型
  var l2 = <String>["张三","李四"];
  var l3 = <int>[12, 30];
  print(l2);
  print(l3);
  // 3、第三种定义List的方式  增加数据 ,通过[]创建的集合它的容量可以变化
  var l4 = [];
  print(l4);
  print(l4.length);
  l4.add("张三");
  l4.add("李四");
  l4.add(20);
  print(l4);
  print(l4.length);

  var l5 = ["张三", 20, true];
  l5.add("李四");
  l5.add("zhaosi");
  print(l5);
  // 4、第四种定义List的方式,通过List.filled创建的集合长度是固定
  var l6 = List.filled(2, "");
  print(l6.length);
  l6.length = 0;  // 修改集合的长度   报错

  var l7 = <String>["张三","李四"];
  print(l7.length);  //2
  l7.length=0;  //可以改变的
  print(l7);  //[]

  var l8 = List<String>.filled(2, "");
  l8[0]="string";
  // l8[0]=222;
  print(l8);			
 // 通过List.from创建
 final numbers = <num>[1, 2, 3];
 final listFrom = List<int>.from(numbers);
 print(listFrom); // [1, 2, 3]
// 通过List.of创建
final numbers = <int>[1, 2, 3];
final listOf = List<num>.of(numbers);
print(listOf); // [1, 2, 3]
// 通过List.generate创建
final growableList =
    List<int>.generate(3, (int index) => index * index, growable: true);
print(growableList); // [0, 1, 4]

final fixedLengthList =
    List<int>.generate(3, (int index) => index * index, growable: false);
print(fixedLengthList); // [0, 1, 4]
// 通过List.unmodifiable创建
final numbers = <int>[1, 2, 3];
final unmodifiableList = List.unmodifiable(numbers); // [1, 2, 3]
unmodifiableList[1] = 87; // 报错 不可修改

Various operation methods of List:

void listAdd() {
    
    
  List<String> list = ["周一", "周二", "周三"];
  list.add("周四");
  list.add("周五");
  print(list);
  List<String> l1 = ["周一", "周二", "周三"];
  List<String> l2 = ["周四", "周五", "周六", "周日"];
  l1.addAll(l2);
  print(l1); // [周一, 周二, 周三, 周四, 周五, 周六, 周日]
}

void listInsert() {
    
    
  List<String> l1 = ["周一", "周二", "周三"];
  l1.insert(3, "周四");
  print(l1); // [周一, 周二, 周三, 周四]
  l1.insert(0, "周日");
  print(l1); // [周日, 周一, 周二, 周三, 周四]
}

void listInsert2() {
    
    
  List<String> l1 = ["周一", "周二", "周三"];
  List<String> l2 = ["周四", "周五", "周六", "周日"];
  l1.insertAll(1, l2);
  print(l1); // [周一, 周四, 周五, 周六, 周日, 周二, 周三]
}

void listRemove() {
    
    
  List<String> l1 = ["周一", "周二", "周三", "周一"];
  l1.remove("周一");
  print(l1); // [周二, 周三, 周一]
  l1.removeAt(1);
  print(l1);
  l1.removeLast();
  print(l1);
  // l1.clear();
}

//根据条件删除元素
void listRemoveWhere() {
    
    
  List<String> l1 = ["周一", "周二", "周三"];
  l1.removeWhere((e) => e == "周二");
  print(l1); // [周一, 周三]

  List<String> l2 = ["周一", "周二", "周三", "星期四"];
  l2.removeWhere((e) => e.contains("周"));
  print(l2); // [星期四]

  List<String> l3 = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
  l3.removeRange(2, 5); //包前不包后
  print(l3); // [周一, 周二, 周六, 周日]
}

void listSet() {
    
    
  List<String> l1 = ["周一", "周二", "周三", "星期四"];
  l1[3] = "周四";
  print(l1); // [周一, 周二, 周三, 周四]

  List<String> l2 = ["周一", "周二", "周三", "周四", "周五"];
  l2.fillRange(1, 4, "AllenSu"); //用相同的值替换指定索引范围内的所有元素(含头不含尾)
  print(l2); // [周一, AllenSu, AllenSu, AllenSu, 周五]

  List<String> l3 = ["周一", "周二", "周三"];
  l3.replaceRange(1, 2, ["周四", "周五", "周六", "周日"]); // 用某一数组替换指定索引范围内的所有元素(含头不含尾)
  print(l3); // [周一, 周四, 周五, 周六, 周日, 周三]

  List<String> l4 = ["周一", "周二", "周三", "周四", "周五"];
  l4.replaceRange(0, 4, ["周六", "周日"]);
  print(l4); // [周六, 周日, 周五]

  List<String> l5 = ["11", "22", "33"];
  List<String> l6 = ["aa", "bb", "cc"];
  l5.setRange(0, 2, l6); // 用 l6 数组中索引为 0 1 位置处元素的值,替换掉 l5 数组中索引 0 1 位置处元素的值
  print(l5); // [aa, bb, 33]

  List<String> l7 = ["周一", "周二", "周三", "周四"];
  List<String> l8 = ["周五", "周六", "周日"];
  l7.setAll(1,
      l8); // 从指定索引位置开始,使用第二个数组内的元素依次替换掉第一个数组中的元素 注意: index + l2.length <= l1.length ,否则会报错。
  print(l7); // [周一, 周五, 周六, 周日]
}

void listSearch() {
    
    
  List<String> l1 = ["周一", "周二", "周三", "周四"];
  String str = l1.elementAt(2);
  print(str); // 周三
  print(l1[2]); // 周三
  print(l1.first); // 周一
  print(l1.last); // 周四

  List<String> l2 = ["周一", "周二", "周三", "周四", "周三"];
  bool a = l2.contains("周一");
  bool b = l2.contains("周日");
  print(a); // true
  print(b); // false

  print(l2.indexOf("周三")); //2
  print(l2.indexOf("周日")); //-1
  print(l2.lastIndexOf("周三")); //4
  print(l2.lastIndexOf("周日")); //-1
  print(l2.indexWhere((e) => e == "周三")); //2 返回第一个满足条件的元素的索引
  print(l2.lastIndexWhere((e) => e == "周三")); //4 从后往前找 返回第一个满足条件的元素的索引

  List<int> l3 = [8, 12, 4, 1, 17, 33, 10];
  Iterable<int> l4 =
      l3.where((e) => e > 10); //根据条件函数筛选每个元素,符合条件的元素组成一个新的 Iterable
  print(l4); // (12, 17, 33)
  List<int> l5 = l3.where((e) => e >= 10).toList();
  print(l5); // [12, 17, 33, 10]

  print(l3.firstWhere((e) => e > 10)); // 12 返回第一个满足条件的元素
  print(l3.lastWhere((e) => e < 10)); // 1 从后往前找 返回第一个满足条件的元素

  int res1 = l3.singleWhere((e) => e > 30); //获取满足指定条件的唯一元素
  int res2 = l3.singleWhere((e) => e > 33, orElse: () => -1);
  // int res3 = l3.singleWhere((e) => e > 10, orElse: () => null);
  print(res1); // 33
  print(res2); // -1。数组中没有满足条件的元素,返回 orElse 方法指定的返回值(-1是自己填的,你也可以设置为其它值)
  // print(res3); // 报错。当数组中有多个满足条件的元素时,即使你设置了 orElse 的返回值,用 singleWhere 也会报错

  List<int> l6 = [8, 12, 4, 1, 17, 33, 10];
  l6.retainWhere((e) => e > 10); // 保留满足条件的元素(改变了原数组)
  print(l6); // [12, 17, 33]

  List<int> l7 = [8, 12, 4, 1, 17, 33, 10];
  print(l7.any((e) => e > 30)); //true 判断数组中是否有满足指定条件的元素
  print(l7.every((e) => e > 5)); //false 判断数组中是否每个元素都满足指定的条件

  List<int> l8 = [8, 12, 4, 1, 17, 33, 10];
  Iterable<int> l9 = l8.take(3); // 从索引 0 位置处,取指定个数的元素
  print(l9); // (8, 12, 4)

  //从索引 0 位置处,查询满足指定条件的元素,直到出现第一个不符合条件的元素,然后返回前面符合条件的元素
  Iterable<int> l10 = l8.takeWhile((e) => e > 1);
  print(l10); // (8, 12, 4)

  Iterable<int> l11 = l8.skip(4); // 跳过指定个数的元素,返回后面的元素
  print(l11); // (17, 33, 10)

  Iterable<int> l12 =
      l8.skipWhile((e) => e < 17); //跳过满足指定条件的元素,直到不满足条件的元素,然后将该元素后面的所有元素返回
  print(l12); // (17, 33, 10)

  List<int> l13 = l8.sublist(5); //从指定索引处截取数组
  print(l13); // [33, 10]

  List<int> l14 = l8.sublist(2, 5);
  print(l14); // [4, 1, 17] 含头不含尾

  Iterable<int> l15 = l8.getRange(2, 5); //截取指定索引范围内的元素
  print(l15); // (4, 1, 17)

  List list = [
    "a",
    15,
    "b",
    false,
    true,
    20,
    "c",
    {
    
    "name": "AllenSu"}
  ];
  Iterable<String> list1 = list.whereType(); // 从混合类型的数组中,筛选出指定类型的元素
  Iterable<int> list2 = list.whereType();
  Iterable<bool> list3 = list.whereType();
  Iterable<Map> list4 = list.whereType();
  print(list1); // (a, b, c)
  print(list2); // (15, 20)
  print(list3); // (false, true)
  print(list4); // ({name: AllenSu})
}

void listOperate() {
    
    
  List<int> l1 = [8, 12, 4, 6, 22, 35, 10];
  l1.forEach((e) => print(e)); 
  for(var i = 0; i < l1.length; i++) {
    
    
     print(l1[i]);
  }
  for (var item in l1) {
    
    
     print(item);
  }
  Iterable<bool> l2 =
      l1.map((e) => e > 10); //遍历数组中的所有元素,可以对元素进行处理,并返回新的 Iterable
  Iterable<String> l3 = l1.map((e) => e > 10 ? "大" : "小");
  Iterable<num> l4 = l1.map((e) => e > 12 ? e / 2 : e * 2);
  List<String> l5 = l1.map((e) => e % 2 == 0 ? "even" : "odd").toList();
  print(l2); // (false, true, false, false, true, true, false)
  print(l3); // (小, 大, 小, 小, 大, 大, 小)
  print(l4); // (16, 24, 8, 12, 11.0, 17.5, 20)
  print(l5); // [even, even, even, even, even, odd, even]

  List<int> l7 = [8, 12, 8, 6, 22, 12, 10];
  Set<int> l8 = l7.toSet(); //去重
  print(l8); // {8, 12, 6, 22, 10}

  List<String> l9 = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
  Map map = l9.asMap(); // 将 List 转换为 Map,key 为原数组的索引,value 为原数组的元素
  print(map); // {0: 周一, 1: 周二, 2: 周三, 3: 周四, 4: 周五, 5: 周六, 6: 周日}

  List<int> sortList1 = [8, 12, 8, 6, 22, 12, 10];
  sortList1.sort(); //排序
  List<String> sortList2 = ["f", "d", "b", "c", "a", "e"];
  sortList2.sort();
  print(sortList1); // [6, 8, 8, 10, 12, 12, 22]
  print(sortList2); // [a, b, c, d, e, f]
  print(sortList2.reversed); //反序

  List<int> list1 = [8, 12, 8, 6, 22, 12, 10];
  String str1 = list1.join();
  String str2 = list1.join("");
  String str3 = list1.join(" ");
  String str4 = list1.join(","); //用指定字符连接数组中每个元素,返回 String
  String str5 = list1.join("-");
  print(str1); // 81286221210
  print(str2); // 81286221210
  print(str3); // 8 12 8 6 22 12 10
  print(str4); // 8,12,8,6,22,12,10
  print(str5); // 8-12-8-6-22-12-10

  List<int> intList = [8, 12, 8];
  var anotherList = intList.cast(); // 指定 l2 的数据类型和 l1 的一样,都是 int 类型
  anotherList.add(6);
  print(intList); // [8, 12, 8, 6] 二者是同一个数组引用
  // anotherList.add("ddd");
  // print(intList); // 报错,提示无法将 String 类型的数添加到 int 类型的数组中

  // generate 快速生产 Flutter 中的 Widget
// children: List.generate(l1.length, (index){
    
    
//     return Text("$index");
//  })
  List<String> strList =
      List.generate(l1.length, (index) => "$index ==> ${l1[index]}");
  print(strList);

  //reduce 用指定的函数方式对数组中的所有元素做连续操作,并将结果返回
  List<int> list = [1, 2, 3, 4];
  int res1 = list.reduce((a, b) => (a * b)); // 元素依次相乘
  int res2 = list.reduce((a, b) => (a + b)); // 元素依次相加
  print(res1); // 1*2*3*4 = 24
  print(res2); // 1+2+3+4 = 10

  //fold 根据一个现有数组和一个初始参数值 initValue,指定参数条件操作现有数组的所有元素,并返回处理的结果
  // 2 为初始参数值,后面定义的方法为该初始值和原数组之间的处理方式
  int res3 = list.fold(2, (a, b) => (a * b)); // 相乘
  int res4 = list.fold(2, (a, b) => (a + b)); // 相加
  print(res3); // 2*(1*2*3*4) = 48
  print(res4); // 2+(1+2+3+4) = 12

  // 将 l1 数组内的每一个元素都和指定的表达式组相操作
  Iterable<int> a2 = list.expand((e) => [e + 1]);
  Iterable<int> a3 = list.expand((e) => [e + 1, e + 2]);
  Iterable<int> a4 = list.expand((e) => [e + 2, e * 2]);
  Iterable<num> a5 = list.expand((e) => [e * 2, e / 2]);
  Iterable<int> a6 = list.expand((e) => [e, e + 1, e + 2]);
  print(a2); // (2, 3, 4, 5)
  print(a3); // (2, 3, 3, 4, 4, 5, 5, 6)
  print(a4); // (3, 2, 4, 4, 5, 6, 6, 8)
  print(a5); // (2, 0.5, 4, 1.0, 6, 1.5, 8, 2.0)
  print(a6); // (1, 2, 3, 2, 3, 4, 3, 4, 5, 4, 5, 6)

  list.shuffle(); //随机变换数组元素顺序
  print(list); // [1, 4, 2, 3]

  print(list.isEmpty);
  print(list.isNotEmpty);
}

Map

How to define a Map

    //第一种定义Map的方式 有点像js定义json对象的方式
    var person = {
    
    
      "name": "张三",
      "age": 20,
      "work": ["程序员", "送外卖"]
    };
    print(person);
    print(person["name"]);
    print(person["age"]);
    print(person["work"]);
   //第二种定义Map的方式
    var p = new Map();
    p["name"] = "李四";
    p["age"] = 22;
    p["work"] = ["程序员", "送外卖"];
    print(p);
    print(p["age"]);

Common properties of Map:

	Map person = {
    
    
      "name": "张三",
      "age": 20,
      "sex": "男"
    };
    print(person.keys.toList());
    print(person.values.toList());
    print(person.isEmpty);
    print(person.isNotEmpty);

Map common methods:

    Map person = {
    
    
      "name": "张三",
      "age": 20,
      "sex": "男"
    };
    // 合并映射  给映射内增加属性
    person.addAll({
    
     "work": ['敲代码', '送外卖'], "height": 160}); 
    print(person);
    person.remove("sex"); // 删除指定key的数据
    print(person);
    print(person.containsValue('张三')); // 查看映射内的值  返回true/false
    person.forEach((key, value) {
    
    
    	print("$key---$value");
  	});

Set

Its main function is to remove duplicates from arrays. Set is an unordered and non-repeatable collection, so values ​​cannot be obtained through indexes.

  var s = new Set();
  s.add('香蕉');
  s.add('苹果');
  s.add('苹果');
  print(s);   //{香蕉, 苹果}
  print(s.toList()); 

  List myList=['香蕉','苹果','西瓜','香蕉','苹果','香蕉','苹果'];
  var s = new Set();
  s.addAll(myList);
  print(s);
  print(s.toList());
  s.forEach((value)=>print(value));
// set1.difference(set2):返回set1集合里有但set2里没有的元素集合
// set1.intersection(set2):返回set1和set2的交集
// set1.union(set2):返回set1和set2的并集
// set1.retainAll():set1只保留某些元素(要保留的元素要在原set中存在)

type judgment

  var str=123;
  if (str is String) {
    
    
    print('是string类型');
  } else if(str is int) {
    
    
     print('int');
  } else {
    
    
     print('其他类型');
  }

  Person p = Person('张三', 20);
  if (p is Person) {
    
    
    p.name = "李四";
  }
  p.printInfo();
  print(p is Object); //true

Cast type:

  var p1;
  p1 = '';
  
  p1 = new Person('张三1', 20);
  p1.printInfo();
  
  (p1 as Person).printInfo(); // 强转

operators and operators

Arithmetic operators:

The division in Dart is a real division, which is different from java's rounding

  int a = 13;
  int b = 5;
  print(a + b);   // 加
  print(a - b);   // 减
  print(a * b);   // 乘
  print(a / b);  // 2.6 除  
  print(a ~/ b); // 2 取整
  print(a % b);  // 3 取余

??Assignment operator:

bool? b;
b ??= false; // 如果b为空时,将false赋值给b,否则,b的值保持不变。
print(b);

//  a ?? b
// 如果 a != null,返回 a 的值; 否则返回 b 的值。
// 如果赋值是基于判定是否为 null, 考虑使用 ??。
String playerName = p.name ?? 'Guest'; // 等价于:name != null ? name : 'Guest';

Compound assignment operator: += -= *= /= %= ~/=use the same as java

Relational operators: == != > < >= <=use the same as java

Logical operators: || && !use the same as java

Conditional expressions: if-else, switch-case, 三目运算符 c = flg ?a :buse the same as java

Self-increment and self-decrement operators: ++ --use the same as java

?.non-null operator:

Person p = Person('张三', 20);
// p不为null才执行右边的
p?.printInfo();

..Cascading operators:

 Person p1 = new Person('张三1', 20)
    ..name = "李四"
    ..age = 30
    ..printInfo();
    
  // 上面等价下面的
  //  Person p1 = new Person('张三1', 20);
  //  p1.printInfo();
  //  p1.name='张三222';
  //  p1.age=40;
  //  p1.printInfo();

function

Define a method and print user information:

String printUserInfo(String username, int age) {
    
     //行参 
	return "姓名:$username---年龄:$age";
}
print(printUserInfo('张三', 20)); //实参

Define a method with optional parameters or default parameter values[] ​​(optional parameters use specified), the latest dart definition optional parameters need to specify the type default value:

String printUserInfo(String username,[String sex = '男',int age = 0]) {
    
      
    if (age != 0) {
    
    
      return "姓名:$username---性别:$sex--年龄:$age";
    }
    return "姓名:$username---性别:$sex--年龄保密";
}
print(printUserInfo('张三'));
print(printUserInfo('小李','女'));
print(printUserInfo('小李','女',30));

Define a method with named parameters (named parameters are {}specified), the latest dart defines named parameters that need to specify the default value of the type:

String printUserInfo(String username, {
    
    int age = 0, String sex = '男'}) {
    
     
     return "姓名:$username-性别:$sex-年龄:${age}";
}
print(printUserInfo('张三', age: 20, sex: '女'));

Define a method that takes a method as a function parameter :

  //fn1方法
  fn1() {
    
    
    print('fn1');
  }
  //fn2方法
  fn2(fn) {
    
    
    fn();
  }
  //调用fn2这个方法 把fn1这个方法当做参数传入
  fn2(fn1);

Anonymous method :

var printNum = (int n, int m) => print(m * n); // 匿名方法
printNum(1, 2); // 调用
var printNum2 = () => print("aaaaa"); // 无参匿名函数
printNum2 (); // 调用

Define a function that returns a function type :

/// 返回一个函数,返回的函数参数与 [addBy] 相加。
Function makeAdder(num addBy) {
    
    
  return (num i) => addBy + i;
}

void testMakeAdder() {
    
    
  // 创建一个加 2 的函数。
  var add2 = makeAdder(2);

  // 创建一个加 4 的函数。
  var add4 = makeAdder(4);

  print(add2(3) == 5); //true
  print(add4(3) == 7); //true
}

Arrow function :

// 需求:使用forEach打印下面List里面的数据
List list = ['苹果','香蕉','西瓜'];

list.forEach((value){
    
    
  print(value);
});

// 注意和方法的区别: 箭头函数内只能写一条语句,并且语句后面没有分号(;)
list.forEach((value) => print(value) );
// 需求:修改下面List里面的数据,让数组中大于2的值乘以2
List list = [4,1,2,3,4];

var newList = list.map((value) {
    
    
    if (value > 2) {
    
    
      return value * 2;
    }
    return value;
});
print(newList.toList());

var newList = list.map((value) => value> 2 ? value * 2 : value);
print(newList.toList());

Function alias :

//函数别名 使用typedef给函数定义一个名字代替使用
typedef Fun1(int a, int b);
typedef Fun2<T, K>(T a, K b);
int add(int a, int b) {
    
    
  return a + b;
}

class Demo1 {
    
    
  Demo1(int f(int a, int b), int x, int y) {
    
    
    var sum = f(x, y);
    print("sum1 = $sum");
  }
}

class Demo2 {
    
    
  Demo2(Fun1 f, int x, int y) {
    
    
    var sum = f(x, y);
    print("sum2 = $sum");
  }
}

class Demo3 {
    
    
  Demo3(Fun2<int, int> f, int x, int y) {
    
    
    var sum = f(x, y);
    print("sum3 = $sum");
  }
}

void main() {
    
    
  Demo2 demo2 = Demo2(add, 2, 3);
  Demo3 demo3 = Demo3(add, 4, 5);
}

Closure :

  • Features of global variables: Global variables are resident in memory and pollute the whole world
  • Features of local variables: non-resident memory will be recycled by the garbage mechanism and will not pollute the global

Problems solved by closures:

  1. resident memory
  2. Do not pollute the whole world

The essence of closure: function nesting function, the inner function will call the variables or parameters of the outer function, the variables or parameters will not be recycled by the system (the memory will not be released)

The way of writing the closure: the function nests the function, and returns the function inside, thus forming a closure.

void main() {
    
    
 fn() {
    
    
    var a = 123; /*不会污染全局   常驻内存*/
    return () {
    
    
      a++;
      print(a);
    };
  }

  var b = fn();
  b();
  b();
  b();

  // print(a); // 其他地方访问不到a
}

Null safety

Null safety translated into Chinese means empty safety. Null safety can help developers avoid some hard-to-find mistakes in daily development, and the added benefit is that it can improve performance.

Versions after Flutter2.2.0 (released on May 19, 2021) require the use of null safety.

Dart uses ?to represent the nullable type, with after the type ?to represent the nullable type, and without it to represent the non-nullable type. This is the same as kotlin.

nullcannot be assigned to a non-null type

String username = "张三";   
username = null; // 报错

List<String> l1 = ["张三","李四","王五"];
l1 = null;  // 报错
String? username = "张三";   // String?  表示username是一个可空类型
username = null; // ok
print(username);

int? a = 123;  //  int? 表示a是一个可空类型
a = null; // ok
print(a);

List<String>? l1 = ["张三","李四","王五"];
l1 = null;  // ok
print(l1);
String? getData(apiUrl){
    
    
  if(apiUrl!=null){
    
    
    return "this is server data";
  }
  return null;
}
//调用方法
print(getData("http://www.itying.com"));
print(getData(null));

Dart uses !to represent type assertion

void printLength(String? str){
    
    
  try {
    
    
    //类型断言: 如果str不等于null 会打印str的长度,如果等于null会抛出异常
    print(str!.length); 
  } catch (e) {
    
    
     print("str is null"); 
  }
}  
printLength("str");
printLength(null);

late Lazy initialization

It is very similar to kotlin's lateinit, but kotlin's lateinit cannot modify basic types, while dart can.

class Person {
    
    
  late String name;
  late int age;
  void setName(String name, int age) {
    
    
    this.name = name;
    this.age = age;
  }
  String getName() {
    
    
    return "${this.name}---${this.age}";
  }
}

void main(args) {
    
    
  Person p = new Person();
  p.setName("张三", 20);
  print(p.getName());
}

required

requiredTranslated into Chinese means need, dependence

requiredKeywords: At first @requiredit was an annotation, and now it has been used as a built-in modifier.

Mainly used to allow any named parameters (function or class) to be marked as required such that they are not null. Because there must be a required parameter among the optional parameters or the parameter has a default value.

String printUserInfo(String username, {
    
    int age=10, String sex="男"}) {
    
     
  return "姓名:$username---性别:$sex--年龄:$age";
}

String printInfo(String username, {
    
    required int age, required String sex}) {
    
     
  return "姓名:$username---性别:$sex--年龄:$age";
}

void main(args) {
    
    
    // 不加required的命名参数,可以不传,可以只传部分
    print(printUserInfo('张三'));
    print(printUserInfo('张三', age: 20)); 
    
    //加required:age 和 sex必须传入
    print(printInfo('张三',age: 22,sex: "女"));
}

Used in the constructor parameters of the class required(constructors in Flutter components are often written like this):

class Person {
    
    
  String name;
  int age;
  // {}表示 name 和age 是必须传入的命名参数
  // required表示 name 和age 必须传入
  Person({
    
    required this.name, required this.age});  

  String getName() {
    
    
    return "${this.name}---${this.age}";
  }
}

void main(args) {
    
    
   Person p = new Person(name: "张三", age: 20);
   print(p.getName());
}
class Person {
    
    
  String? name;   //可空属性
  int age;
  Person({
    
    this.name, required this.age});  //可空的参数不用加required

  String getName() {
    
    
    return "${this.name}---${this.age}";
  }
}

void main(args) {
    
    
   Person p = new Person(age: 20);
   print(p.getName());  //张三---20

   Person p1 = new Person(age: 20);
   print(p1.getName());  //null---20
}

object oriented

Everything in Dart is an object, and all objects inherit from the Object class.

kind

Dart is an object-oriented language that uses classes and single inheritance. All objects are instances of classes, and all classes are subclasses of Object.

A class usually consists of properties and methods.

class Person{
    
    
  String name = "张三";
  int age = 23;
  void getInfo() {
    
    
      // print("$name----$age");
      print("${this.name}----${this.age}");
  }
  void setInfo(int age) {
    
    
    this.age=age;
  }
}

void main(){
    
    
  //实例化  
  Person p1=new Person();
  p1.setInfo(28);
  p1.getInfo();

  print(p1.name);
  p1.getInfo();
}

Constructor

Default no-argument constructor :

class Person{
    
    
  String name='张三';
  int age=20; 
  //默认构造函数
  Person(){
    
    
    print('这是构造函数里面的内容  这个方法在实例化的时候触发');
  }
  void printInfo(){
    
       
    print("${this.name}----${this.age}");
  }
}

Ordinary constructor:

// 注意:最新版本的dart中需要初始化不可为null的实例字段,如果不初始化的话需要在属性前面加上late
class Person{
    
    
  late String name;
  late int age; 

  Person(String name,int age){
    
    
      this.name=name;
      this.age=age;
  }
  void printInfo(){
    
       
    print("${this.name}----${this.age}");
  }
}

Shorthand for the constructor :

class Person{
    
    
  late String name;
  late int age;  
  Person(this.name,this.age);
  void printInfo(){
    
       
    print("${this.name}----${this.age}");
  }
}
class Container{
    
    
  int width;
  int height;
  Container({
    
    required this.width, required this.height});
}

void main(){
    
    
  var c1 = new Container(width: 100,height: 100);
  var c2 = new Container(width: 100,height: 100); 
}

Named constructor:

class Person {
    
    
  late String name;
  late int age;
  //构造函数的简写
  Person(this.name, this.age);
  Person.now() {
    
    
    print('我是命名构造函数');
  }
  //命名构造函数传参
  Person.setInfo(String name, int age) {
    
    
    this.name = name;
    this.age = age;
  }
  void printInfo() {
    
    
    print("${this.name}----${this.age}");
  }
}

void main() {
    
    
	Person p1 = new Person('张三', 20);   //默认实例化类的时候调用的是 默认构造函数
	
	var d = new DateTime.now();   //实例化DateTime调用它的命名构造函数
	print(d);
    
    Person p1 = new Person.now();   //调用命名构造函数

  	Person p1 = new Person.setInfo('李四', 30);  //调用命名构造函数
  	p1.printInfo();
}

There can be multiple constructors in dart.

Factory constructor :

The factory constructor is a kind of constructor. Unlike ordinary constructors, the factory function does not automatically generate an instance, but determines the returned instance object through code.

If a constructor does not always return a new object (singleton), use factoryto define the constructor. The factory constructor cannot be accessed this.

class Singleton {
    
    
  String name;
  //工厂构造函数无法访问this,所以这里要用static
  static Singleton? _cache;

  //工厂方法构造函数,关键字factory
  factory Singleton([String name = 'singleton']) =>
      Singleton._cache ??= Singleton._newObject(name);

  //定义一个命名构造函数用来生产实例
  Singleton._newObject(this.name);
}

constructor initializer list

In Dart we can also initialize instance variables before the constructor body runs

class Rect {
    
    
  int height;
  int width;
  Rect(): height = 2, width = 10 {
    
        
    print("${this.height}---${this.width}");
  }
  getArea() {
    
    
    return this.height * this.width;
  } 
}

void main() {
    
    
  Rect r = new Rect();
  print(r.getArea()); 
}
class Point {
    
    
  //final变量不能被修改,必须被构造函数初始化
  final num x;
  final num y;
  final num distanceFromOrigin;

  //初始化列表构造函数
  Point(x, y)
      : x = x,
        y = y,
        distanceFromOrigin = sqrt(x * x + y * y);
}

import class

You can put the class .dartin a separate file and import and use it in other files

import 'lib/Person.dart';

void main(){
    
    
  Person p1=new Person.setInfo('李四1',30);
  p1.printInfo(); 
}

Private Methods and Private Properties

Unlike other object-oriented languages, Dart does not have public private protectedthese access modifiers in Data

But we can use underscore _to define a property or method as private .

// Animal.dart
class Animal{
    
    
  late String _name;   //私有属性
  late int age; 
  //默认构造函数的简写
  Animal(this._name,this.age);

  void printInfo(){
    
       
    print("${this._name}----${this.age}");
  }

  String getName(){
    
     
    return this._name;
  } 
  void _run(){
    
    
    print('这是一个私有方法');
  }

  execRun(){
    
    
    this._run();  //类里面方法的相互调用
  }
}
import 'lib/Animal.dart';

void main() {
    
    
	 Animal a=new Animal('小狗', 3);
	
	 print(a.getName());
	
	 a.execRun();   //间接的调用私有方法
	 // a._run(); // 访问不到私有方法
	 // print(a._name); // 访问不到私有成员
	 print(a.age);
}

getter和setter

// 常规方式
class Rect {
    
    
  num height;
  num width;   
  Rect(this.height, this.width);
  area() {
    
    
    return this.height * this.width;
  }
}
void main() {
    
    
  Rect r = new Rect(10,4);
  print("面积:${r.area()}");   
}
// getter和setter 方式
class Rect {
    
    
  late num height;
  late num width;   
  Rect(this.height, this.width);
  get area {
    
    
    return this.height * this.width;
  }
  set areaHeight(value) {
    
    
    this.height = value;
  }
}
void main() {
    
    
  Rect r=new Rect(10,4);
  //注意调用直接通过访问属性的方式访问area
  r.areaHeight = 6;
  print("面积:${r.area}");
}

static members and static methods

There is no difference in use from java

class Person {
    
    
  static String name = '张三';
  static void show() {
    
    
    print(name);
  }
}

main(){
    
    
  print(Person.name);
  Person.show();
}
class Person {
    
    
  static String name = '张三';
  int age=20;  
  static void show() {
    
    
    print(name);
  }
  void printInfo(){
    
      /*非静态方法可以访问静态成员以及非静态成员*/
      // print(name);  //访问静态属性
      // print(this.age);  //访问非静态属性
      show();   //调用静态方法
  }
  static void printUserInfo(){
    
    //静态方法
        print(name);   //静态属性
        show();        //静态方法
        //print(this.age);     //静态方法没法访问非静态的属性
        // this.printInfo();   //静态方法没法访问非静态的方法
        // printInfo();
  }
}

main(){
    
    
  // print(Person.name);
  // Person.show(); 

  // Person p=new Person();
  // p.printInfo(); 

  Person.printUserInfo();
}

inherit

The three major characteristics of object-oriented: encapsulation, inheritance, polymorphism

Inheritance of classes in Dart:

  • 1. The subclass uses the extends keyword to inherit the parent class
  • 2. The subclass will inherit the properties and methods visible in the parent class but will not inherit the constructor
  • 3. Subclasses can override the method getter and setter of the parent class
class Person {
    
    
  String name='张三';
  num age=20; 
  void printInfo() {
    
    
    print("${this.name}---${this.age}");  
  } 
}
class Man extends Person {
    
    
}

main() {
    
    
  Man m = new Man();
  print(m.name);
  m.printInfo(); 
}

Call the parent class constructor:

class Person {
    
    
  late String name;
  late num age; 
  Person(this.name,this.age);
  void printInfo() {
    
    
    print("${this.name}---${this.age}");  
  }
}
class Man extends Person {
    
    
  Man(String name, num age) : super(name, age) {
    
        
  }  
}
class Person {
    
    
  String name;
  num age;
  Person(this.name, this.age);
  Person.from(this.name, this.age);
  void printInfo() {
    
    
    print("${this.name}---${this.age}");
  }
}

class Man extends Person {
    
    
  late String sex;
  // 调用父类命名构造函数
  Man(String name, num age, String sex) : super.from(name, age) {
    
    
    this.sex = sex;
  }
  run() {
    
    
    print("${this.name}---${this.age}--${this.sex}");
  }
}

main() {
    
     
  Man m = new Man('张三', 12, "男");
  m.printInfo();
  m.run();
}

Override the method of the parent class:

class Person {
    
    
  String name;
  num age; 
  Person(this.name,this.age);
  void printInfo() {
    
    
    print("${this.name}---${this.age}");  
  }
  work(){
    
    
    print("${this.name}在工作...");
  }
}

class Man extends Person{
    
    
  Man(String name, num age) : super(name, age);
  run(){
    
    
    print('run');
  } 
   // 可以写也可以不写  建议在覆写父类方法的时候加上 @override 
  void printInfo(){
    
    
     print("姓名:${this.name}---年龄:${this.age}"); 
  }
  
  work(){
    
    
    print("${this.name}的工作是写代码");
  }
}

main(){
    
    
  Man m = new Man('李四',20);
  m.printInfo();
  m.work(); 
}

The subclass calls the method of the superclass:

class Person {
    
    
  String name;
  num age; 
  Person(this.name,this.age);
  void printInfo() {
    
    
    print("${this.name}---${this.age}");  
  }
  work(){
    
    
    print("${this.name}在工作...");
  }
}

class Man extends Person {
    
    
  Man(String name, num age) : super(name, age);
  run(){
    
    
    print('run');
    super.work();  //子类调用父类的方法
  } 
}

main(){
    
    
  Man m = new Man('李四',20); 
  m.run(); 
}

abstract class

Dart abstract classes are mainly used to define standards, subclasses can inherit abstract classes, and can also implement abstract class interfaces.

  • 1. Abstract classes abstractare defined by keywords

  • 2. Abstract methods in Dart cannot be abstractdeclared. Methods without method bodies in Dart are called abstract methods.

  • 3. If the subclass inherits the abstract class, it must implement the abstract method inside

  • 4. If you implement an abstract class as an interface, you must implement all the properties and methods defined in the abstract class.

  • 5. An abstract class cannot be instantiated, only subclasses that inherit it can

extendsThe difference between an abstract class and implements:

  • 1. If we want to reuse the methods in the abstract class and use the abstract method to constrain the self-class, we use extendsthe inheritance abstract class

  • 2. If we only regard abstract classes as standards, we will use implementsthe implementation of abstract classes

Case: Defining a Animalclass requires that its subclasses must contain eatmethods

abstract class Animal {
    
    
  eat();   //抽象方法
  run();  //抽象方法  
  printInfo(){
    
    
    print('我是一个抽象类里面的普通方法');
  }
}

class Dog extends Animal{
    
    
  
  eat() {
    
    
     print('小狗在吃骨头');
  }

  
  run() {
    
    
    // TODO: implement run
    print('小狗在跑');
  }  
}
class Cat extends Animal{
    
    
  
  eat() {
    
    
    // TODO: implement eat
    print('小猫在吃老鼠');
  }

  
  run() {
    
    
    // TODO: implement run
    print('小猫在跑');
  }
}

main(){
    
    
  Dog d=new Dog();
  d.eat();
  d.printInfo();

  Cat c=new Cat();
  c.eat();
  c.printInfo();

  // Animal a=new Animal();   //抽象类没法直接被实例化
}

polymorphism

Like java, it is allowed to use the reference of the parent type to receive instances of the subtype. Polymorphism means that the parent class first defines a method but does not implement it, and let the subclass implement it. Different subclasses have different implementations, so the reference of the parent type The function call will have different execution effects at runtime, and its behavior is determined by the subclass actually pointed to at runtime.

abstract class Animal{
    
    
  eat();   //抽象方法 
}

class Dog extends Animal{
    
    
  
  eat() {
    
    
     print('小狗在吃骨头');
  }
  run(){
    
    
    print('run');
  }
}
class Cat extends Animal{
    
    
  
  eat() {
    
       
    print('小猫在吃老鼠');
  }
  run(){
    
    
    print('run');
  }
}

main(){
    
     
  Animal d = new Dog();
  d.eat();

  Animal c = new Cat();
  c.eat();
}

interface

Like Java, dart also has interfaces, but it is still different from Java.

  • First of all, dart's interface does not have interfacea keyword to define the interface, but ordinary classes or abstract classes can be implemented as interfaces.

  • implementsThe same is done using keywords.

But the interface of dart is a bit strange. If the implemented class is an ordinary class, all the methods of the ordinary class and the attributes in the abstract will need to be overwritten.

And because abstract classes can define abstract methods, ordinary classes cannot, so generally if you want to implement a method like a Java interface, you will generally use abstract classes. It is recommended to define interfaces using abstract classes.

/**
 * 定义一个DB库 支持 mysql  mssql  mongodb
 * mysql  mssql  mongodb三个类里面都有同样的方法
*/
abstract class Db {
    
       //当做接口   接口:就是约定 、规范
    late String uri;      //数据库的链接地址
    add(String data);
    save();
    delete();
}

class Mysql implements Db {
    
    
  
  
  String uri;

  Mysql(this.uri);

  
  add(data) {
    
    
    print('这是mysql的add方法'+data);
  }

  
  delete() {
    
    
    return null;
  }

  
  save() {
    
    
    return null;
  }
  remove(){
    
    
      
  }
}

class MsSql implements Db {
    
    
  
  
  late String uri;
  
  
  add(String data) {
    
    
    print('这是mssql的add方法'+data);
  }

  
  delete() {
    
    
    return null;
  }

  
  save() {
    
    
    return null;
  }
}

main() {
    
    
  Mysql mysql=new Mysql('xxxxxx');
  mysql.add('1243214');
}

A class in Dart implements multiple interfaces:

abstract class A {
    
    
  late String name;
  printA();
}

abstract class B {
    
    
  printB();
}

class C implements A, B {
    
      
  
  late String name;  
  
  printA() {
    
    
    print('printA');
  }
  
  printB() {
    
    
    return null;
  }
}

void main(){
    
    
  C c = new C();
  c.printA();
}

Use ordinary classes as interfaces:

class D {
    
    
  printD() {
    
    
    print("aa");
  }
}
// dart中的implements可以实现普通类,它把普通类的方法当做实现接口
class C implements D {
    
     
  
  printD() {
    
    

  }
}

mixedin

The Chinese meaning of mixins is to mix in, which is to mix other functions into the class.

In Dart, mixins can be used to implement functions similar to multiple inheritance

Because the conditions used by mixins (may change with Dart version updates):

  1. Classes that are mixins must use the mixin modifier
  2. Classes used as mixins can only inherit from Object and cannot inherit from other classes
  3. Classes that are mixins cannot have constructors
  4. A class can mixins multiple mixins classes
  5. Mixins are by no means inheritance, nor interface, but a brand new feature
mixin class A {
    
    
  String info="this is A";
  void printA(){
    
    
    print("A");
  }
}

mixin class B {
    
    
  void printB(){
    
    
    print("B");
  }
}

class C with A,B {
    
    
  
}

void main(){
    
    
  var c = new C();  
  c.printA();
  c.printB();
  print(c.info);
  // 下面三个都是true说明c是A和B的子类型,效果上是多继承
  print(c is C);    //true
  print(c is A);    //true
  print(c is B);   //true

}

You can also write directly like this:

mixin A {
    
    
  String info="this is A"; 
} 
class C with A {
    
    
}

Use extendsand withimplement multiple inheritance at the same time:

class Person{
    
    
  String name;
  num age;
  Person(this.name,this.age);
  printInfo(){
    
    
    print('${this.name}----${this.age}');
  }
  void run(){
    
    
    print("Person Run");
  }
}

mixin class A {
    
    
  String info="this is A";
  void printA(){
    
    
    print("A");
  }
  void run(){
    
    
    print("A Run");
  }
}

mixin class B {
    
      
  void printB(){
    
    
    print("B");
  }
  void run(){
    
    
    print("B Run");
  }
}

class C extends Person with B,A {
    
    
  C(String name, num age) : super(name, age);
}

void main(){
    
      
  var c=new C('张三',20);  
  c.printInfo();
  // c.printB();
  // print(c.info);
  c.run();
}

generic

//只能返回string类型的数据
String getData(String value){
    
    
    return value;
}
//同时支持返回 string类型 和int类型  (代码冗余)
String getData1(String value){
    
    
    return value;
}
int getData2(int value){
    
    
    return value;
}
// 同时返回 string类型 和number类型 不指定类型可以解决这个问题
getData(value){
    
    
    return value;
}

This problem can be solved by not specifying the type

// 同时返回 string类型 和number类型  
getData(value){
    
    
    return value;
}

Not specifying a type forgoes type checking. What we want to achieve now is to return what is passed in. For example: the incoming number type must return the number type and the incoming string type must return the string type

T getData<T>(T value) {
    
    
    return value;
}
void main(){
    
     
    getData<String>('你好');
    print(getData<int>(12));
}

Basically similar to java.

Collection Listgeneric class usage:

//案例:把下面类转换成泛型类,要求MyList里面可以增加int类型的数据,也可以增加String类型的数据。
// 但是每次调用增加的类型要统一
/**
class MyList {
  List list = <int>[];
  void add(int value) {
    this.list.add(value);
  }
  List getList() {
    return list;
  }
}
*/

class MyList<T> {
    
    
  List list = <T>[];
  void add(T value) {
    
    
    this.list.add(value);
  }

  List getList() {
    
    
    return list;
  }
}

main() {
    
    
  MyList l1 = new MyList(); // 不指定具体类型时,可以任何类型
  l1.add("张三");
  l1.add(12);
  l1.add(true);
  print(l1.getList());

  MyList l2 = new MyList<String>(); // 指定泛型为String类型
  l2.add("张三1");
  // l2.add(11);  //错误的写法 会报错
  print(l2.getList());

  MyList l3 = new MyList<int>();  // 指定泛型为int类型
  l3.add(11);
  l3.add(12);
  // l3.add("aaaa"); //错误的写法 会报错
  print(l3.getList());
}

System built-in List generic usage:

  List list = List.filled(2, "");
  list[0] = "张三";
  list[1] = "李四";
  print(list);

  List list = List<String>.filled(2, "");
  list[0] = "张三1111";
  list[1] = "李四";
  print(list);

  List list2 = List<int>.filled(2, 0);
  list2[0] = 12;
  list2[1] = 13;
  print(list2);

Generic interface:

/**
Dart中的泛型接口:
    实现数据缓存的功能:有文件缓存、和内存缓存。内存缓存和文件缓存按照接口约束实现。
    1、定义一个泛型接口 约束实现它的子类必须有getByKey(key) 和 setByKey(key,value)
    2、要求setByKey的时候的value的类型和实例化子类的时候指定的类型一致
*/ 
abstract class Cache<T> {
    
    
  getByKey(String key);
  void setByKey(String key, T value);
}

class FileCache<T> implements Cache<T> {
    
    
  
  getByKey(String key) {
    
    
    return null;
  }
  
  void setByKey(String key, T value) {
    
    
    print("我是文件缓存 把key=${key}  value=${value}的数据写入到了文件中");
  }
}

class MemoryCache<T> implements Cache<T> {
    
    
  
  getByKey(String key) {
    
    
    return null;
  }
  
  void setByKey(String key, T value) {
    
    
    print("我是内存缓存 把key=${key}  value=${value} -写入到了内存中");
  }
}

void main() {
    
    
  MemoryCache m1 = new MemoryCache<String>();
  m1.setByKey('index', '首页数据');

  MemoryCache m = new MemoryCache<Map>();
  m.setByKey('index', {
    
    "name": "张三", "age": 20});
}

import library library

When introducing the basic knowledge of Dart, the Dart code is basically written in a file, but it is impossible to write like this in actual development. Modularization is very important, so this requires the use of the concept of the library.

In Dart, library usage importis introduced through keywords.

libraryA directive can create a library, and every Dart file is a library , even if no librarydirective is used to specify it.

There are three main types of libraries in Dart:

  1. Our custom library
    import 'lib/xxx.dart';
  2. System built-in library
    import 'dart:math';
    import 'dart:io';
    import 'dart:convert';
  3. Libraries in the Pub package management system
    https://pub.dev/packages
    https://pub.flutter-io.cn/packages
    https://pub.dartlang.org/flutter/
    • 1. You need to create a new pubspec.yaml in the root directory of your project
    • 2. Configure the name, description, dependencies and other information in the pubspec.yaml file
    • 3. Then run pub get to download the package to the local
    • 4. Introduce the library into the project import 'package:http/http.dart' as http; see the documentation for usage
// Dart中导入自己本地库
import 'lib/Animal.dart';
main(){
    
    
  var a=new Animal('小黑狗', 20);
  print(a.getName());
}
// 导入系统内置库 
// import 'dart:io';
import "dart:math";
main(){
    
    
    print(min(12,23));
    print(max(12,25));
}
// 导入系统内置库实现请求数据httpClient
import 'dart:io';
import 'dart:convert';

void main() async{
    
    
  var result = await getDataFromZhihuAPI();
  print(result);
}

//api接口: http://news-at.zhihu.com/api/3/stories/latest
getDataFromZhihuAPI() async{
    
    
  //1、创建HttpClient对象
  var httpClient = new HttpClient();  
  //2、创建Uri对象
  var uri = new Uri.http('news-at.zhihu.com','/api/3/stories/latest');
  //3、发起请求,等待请求
  var request = await httpClient.getUrl(uri);
  //4、关闭请求,等待响应
  var response = await request.close();
  //5、解码响应的内容
  return await response.transform(utf8.decoder).join();
}

Import library naming conflict resolution

When importing two libraries with the same name identifier, if it is java, we usually specify the specific identifier used by writing the complete package name path, even without import, but it is necessary in Dart import. When there is a conflict, askeywords can be used to specify the prefix of the library. As shown in the following example:

import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;

Element element1 = new Element();           // Uses Element from lib1.
lib2.Element element2 = new lib2.Element(); // Uses Element from lib2.

partial import

If only part of the library needs to be imported, there are two modes:

Mode 1: Import only the required parts and use showkeywords, as shown in the following example:

 import 'package:lib1/lib1.dart' show foo;

Mode 2: Hide unnecessary parts and use hidekeywords, as shown in the following example:

import 'package:lib2/lib2.dart' hide foo;      

In the Java coding process, it often happens that a class calls a method with the same name in two classes. At this time, a fully qualified name is required to call it, but it is not required in Dart. Just use the library prefix when importing.

import 'MyLib1.dart' as lib1; 
import 'MyLib2.dart' as lib2 hide test; 
import 'MyLib3.dart' as lib3 show test; 
 
void main() {
    
    
  var compute = lib1.compute();
  var compute2 = lib2.compute();
  var test = lib3.test();
}

Split the library into multiple files

// mylib.dart
library mylib;

//使用part 把一个库分开到多个 Dart 文件中。
part 'util.dart';
part 'tool.dart';

void printMyLib() => print('mylib');
// util.dart
part of mylib;

void printUtil() => print('util');
// tool.dart
part of mylib;

void printTool() => print('tool');

lazy loading

Also known as lazy loading, it can be loaded when needed. The biggest benefit of lazy loading is that it can reduce the startup time of APP.

Lazy loading is deferred asspecified using keywords, as shown in the following example:

 import 'package:deferred/hello.dart' deferred as hello;

When you need to use it, you need to use loadLibrary()the method to load:

 greet() async {
    
    
   await hello.loadLibrary();
   hello.printGreeting();
 }

identical

dart:coreidenticalUsage of the function in the library : bool identical(Object? a, Object? b)Check if two references point to the same object.

void main() {
    
    
  var o1 = new Object();
  var o2 = new Object();
  print(identical(o1, o2));  // alse, different objects.
  print(identical(o1, o1));   // true, same object

  //表示实例化常量构造函数
  //o1 和 o2共享了存储空间
  var o1 = const Object();
  var o2 = const Object();
  print(identical(o1, o2));  //true 共享存储空间
  print(identical(o1, o1));  //true 共享存储空间

  print(identical([2],[2])); //false 不共享存储空间
  print(identical(const [2],const [2])); //true
  print(identical(const [1],const [2])); //false

  print(identical(2, 1 + 1)); // true
}

Discovery: constWhen keywords create the same object in multiple places, only one object is kept in memory

Shared storage space conditions: 1. Constant, 2. Equal value

//常量构造函数
class Container{
    
    
  final int width;
  final int height;
  const Container({
    
    required this.width,required this.height});
}

// 实例化常量构造函数的时候,多个地方创建这个对象,如果传入的值相同,只会保留一个对象。
void main(){
    
    
  var c1 = Container(width: 100, height: 100);
  var c2 = Container(width: 100, height: 100);
  print(identical(c1, c2)); //false
  
  var c3 = const Container(width: 100, height: 100);
  var c4 = const Container(width: 100, height: 100);
  print(identical(c3, c4)); //true

  var c5 = const Container(width: 100, height: 110);
  var c6 = const Container(width: 120, height: 100);
  print(identical(c5, c6)); //false
}

extension function

Similar to kotlin, dart also supports the use of extension functions. The extension function is mainly for the convenience of dynamically adding methods to a class without having to inherit the class for modification (for example, if you have a class from a third-party library, it lacks a certain method you expect, and you want to add one yourself) .

Syntax for defining extensions:

extension [扩展函数名称] on <type> {
    
    
   // 具体的扩展函数内容定义
}

StringFor example, the following code implements an extension for a system class:

extension NumberParsing on String {
    
    
  int parseInt() {
    
    
    return int.parse(this);
  }

  double parseDouble() {
    
    
    return double.parse(this);
  }
}

Then you can use the extension function like this:

'42'.parseInt();
'42.0'.parseDouble();

When declaring an extension, the name can also be omitted. Unnamed extensions are only visible in the library in which they are declared.

extension on String {
    
    
  bool get isBlank => trim().isEmpty;
}

Extensions can have generic type parameters. For example, here is List<T>code that extends a built-in type:

extension MyFancyList<T> on List<T> {
    
    
  int get doubleLength => length * 2;
  List<T> operator -() => reversed.toList();
  List<List<T>> split(int at) => [sublist(0, at), sublist(at)];
}

Extension functions are very useful, such as the commonly used code for serializing Json entity classes:

class User {
    
    
  final String name;
  final String email;

  User(this.name, this.email);

  User.fromJson(Map<String, dynamic> json)
      : name = json['name'],
        email = json['email'];

  Map<String, dynamic> toJson() => {
    
    
        'name': name,
        'email': email,
      };
}

If this Userclass is not our own, but a third-party, then it is impossible to directly write serialized template code in it. At this time, we can use the extended implementation:

// 原始类不用动
class User {
    
    
  final String name;
  final String email;
  User(this.name, this.email);
}
// 自己工程中动态为其添加扩展
extension on User {
    
     
  User.fromJson(Map<String, dynamic> json)
      : name = json['name'],
        email = json['email'];
  Map<String, dynamic> toJson() => {
    
    
        'name': name,
        'email': email,
      };
}

It can be seen that the two implementations are separated and do not interfere with each other. This actually gives us a solution that can dynamically add functions without modifying the original code.

Many dart package libraries on pub.dev use dart's extension feature to define extension functionality for system classes.

Handling Extension Function Conflicts

If an extension member conflicts with an interface or another extension member, there are two workarounds.

One is to use showor hiderestrict the exposed API:

//  两个库中都定义了 parseInt() 扩展方法
import 'string_apis.dart'; 
import 'string_apis_2.dart' hide NumberParsing2; //  使用 hide 隐藏该库中的扩展
// ··· 
print('42'.parseInt());

Another workaround is to explicitly specify the name when calling, which causes the extension code to look like a wrapper class:

//  两个库中都定义了 parseInt() 扩展方法,但是使用了不同名称
import 'string_apis.dart'; // Contains NumberParsing extension.
import 'string_apis_2.dart'; // Contains NumberParsing2 extension.
// ···
// print('42'.parseInt()); // Doesn't work.
print(NumberParsing('42').parseInt());
print(NumberParsing2('42').parseInt());

If two extensions have the same name, then you may need to import with a prefix:

import 'string_apis.dart';
import 'string_apis_3.dart' as rad;

// ···
// print('42'.parseInt()); // Doesn't work.

// Use the ParseNumbers extension from string_apis.dart.
print(NumberParsing('42').parseInt());
// Use the ParseNumbers extension from string_apis_3.dart.
print(rad.NumberParsing('42').parseInt());
// Only string_apis_3.dart has parseNum().
print('42'.parseNum());

external keyword

externalThe main role of is to separate the declaration from the implementation. It is exactly the same as Kotlin in KMM expect / actual.

The method of modifying the declaration is usually implemented by the underlying sdk according to different platforms (vm, web, etc.) ; externalif it is not implemented externally, it will return null; in this way, the same set of external API declarations can be reused, and then multiple corresponding to different platforms A set of implementation; so whether it is dart for web or dart for vm, it is the same set of API for upper-level development. ( externalThe declared method classdoes not need to be declared as abstract class, so classit can be instantiated directly)

externalThe implementation of the declaration method is @patchimplemented through annotations:


class 类名 {
    
    
  ...
  
  external声明的方法名
  ...
}

How to find the implementation of the flutter external declaration method

externalDeclare the path of the method implementation file, the mobile terminal is in vmthe directory:

flutter sdk目录/bin/cache/dart-sdk/lib/_internal/vm/lib

The web side is in js_runtimethe directory:

flutter sdk目录/bin/cache/dart-sdk/lib/_internal/js_runtime/lib

externalThe naming of the file implemented by the declaration method is generally named as xxx_patch.dart, such as in vm/libthe directory, you can see various xxx_patch.dartfiles.

For example, the commonly used Api for obtaining the current time:

var now = new DateTime.now();
print(now); 

View its source code and find that it is externala statement:

// date_time.dart
external DateTime._now();
DateTime.now() : this._now();

Its real implementation is in flutter sdk安装目录\flutter\bin\cache\dart-sdk\lib\_internal\vm_shared\lib\date_patch.dartthe file:

  
  DateTime._now()
      : isUtc = false,
        _value = _getCurrentMicros();

Dart asynchronous programming

Why Asynchronous Code Matters
Asynchronous operations let your program do work while waiting for another operation to complete. Here are some common asynchronous operations:

  • Get data over the network.
  • Write to the database.
  • Read data from a file.

Such asynchronous computations typically Futureprovide their results in the form of or if the result has multiple parts Stream. These calculations will be introduced into the program asynchronously. To accommodate initial async, other normal Dart functions also need to be made async.

To interact with these asynchronous results, you can use asyncthe and awaitkeywords. Most asynchronous functions are just asynchronous Dart functions, and they may depend deeply on inherently asynchronous computations.

Future

Future represents the result of an asynchronous operation, which is equivalent to JS Promise. Future has two states: unfinished or completed .

  • outstanding : When you call an async function, it returns an outstanding Future. This Futureis waiting for the function's asynchronous operation to complete or throw an error.
  • CompletedFuture : Returns a value if the asynchronous operation was successful . Otherwise, it will complete with an error.

Returns a value: completes Future<T>with Ta value of type . For example, Future<String>a string value is produced. If the Future does not yield a usable value, then the Future's type is Future<void>.

Completed with Error: If the asynchronous operation performed by the function fails for any reason, the Future will complete with an error.

 // 2s后打印,不返回任何值
 Future.delayed(Duration(seconds: 2),() => "hi world");
 
 // 返回一个值
 Future.delayed(Duration(seconds: 2), () {
    
    
    return "hi world!";
  }).then((data) {
    
    
    print(data);
  });
  
 // 返回一个异常
  Future.delayed(Duration(seconds: 2), () {
    
    
    //return "hi world!";
    throw AssertionError("Error");
  }).then((data) {
    
    
    //执行成功会走到这里
    print("success");
  }).catchError((e) {
    
    
    //执行失败会走到这里
    print(e);
  });
  // 同上,另一种写法
  Future.delayed(new Duration(seconds: 2), () {
    
    
    //return "hi world!";
    throw AssertionError("Error");
  }).then((data) {
    
    
    print("success");
  }, onError: (e) {
    
    
    print(e);
  });	

whenComplete : Execute regardless of success or failure

  Future.delayed(new Duration(seconds: 2), () {
    
    
    return "hi world!";
  }).then((data) {
    
    
    //执行成功会走到这里
    print(data);
  }).catchError((e) {
    
    
    //执行失败会走到这里
    print(e);
  }).whenComplete(() {
    
    
    //无论成功或失败都会走到这里
    print("Complete");
  });

wait : Wait for multiple asynchronous tasks to complete before executing

   Future.wait([
	    // 2秒后返回结果
	    Future.delayed(new Duration(seconds: 2), () {
    
    
	      return "hello";
	    }),
	    // 4秒后返回结果
	    Future.delayed(new Duration(seconds: 4), () {
    
    
	      return " world";
	    })
  ]).then((results) {
    
    
    print(results[0] + results[1]);
  }).catchError((e) {
    
    
    print(e);
  });

async and await

These two usages are completely consistent with JS.

Just two things to remember:

  • To define an asynchronous function, add asyncthe keyword before the function body
  • awaitKeywords can only appear in asyncdecorated methods

asyncIt is to make the method asynchronous. awaitIs to wait for the asynchronous method execution to complete. Synchronize asynchronous code.

Define an async method

foo() async {
    
    
  return '假设这里是某种异步任务';
}

awaitCalling an async method using

void main() async {
    
    
  var result = await foo();
  print(result);
} 

If the function has a declared return type, update the type to Future<T>, where Tis the type of the function's return value. If the function does not explicitly return a value, the return type is Future<void>:

Future<void> foo() async {
    
     ··· }

An example of bad asynchronous code:

String createOrderMessage() {
    
    
  var order = fetchUserOrder();
  return 'Your order is: $order';
}

Future<String> fetchUserOrder() = Future.delayed(const Duration(seconds: 2), () => 'Large Latte');

void main() {
    
    
  print('Fetching user order...');
  print(createOrderMessage());
}

Here's why the example fails to print the resulting value:

  • fetchUserOrder()is an asynchronous function that, after a delay, provides a string describing the user's order: "Large Latte".
  • To get the user's order, createOrderMessage()you should call fetchUserOrder()and wait for it to complete. Failed to get the final provided string value because createOrderMessage()there was no wait fetchUserOrder()to complete .createOrderMessage()fetchUserOrder()
  • Instead, createOrderMessage()get a representation of work to be done: an unfinished Future.
  • Because createOrderMessage()the value describing the user's order cannot be obtained, the example fails to “Large Latte”print to the console, instead it prints “Your order is: Instance of '_Future<String>'”.

The correct way to write it is to use asyncand await:

Future<String> createOrderMessage() async {
    
    
  var order = await fetchUserOrder();
  return 'Your order is: $order';
}

Future<String> fetchUserOrder() = Future.delayed(const Duration(seconds: 2), () => 'Large Latte');

Future<void> main() async {
    
    
  print('Fetching user order...');
  print(await createOrderMessage());
}

Use multiple awaitto ensure that multiple asynchronous tasks are executed sequentially :

task() async {
    
    
  try {
    
    
    String id = await login("alice", "******");
    String userInfo = await getUserInfo(id);
    await saveUserInfo(userInfo);
    //执行接下来的操作
  } catch (e) {
    
    
    //错误处理
    print(e);
  }
}

getUserInfo(String id) async {
    
    
   return Future.delayed(Duration(seconds: 2),() => "hi world");
}

saveUserInfo(String id) async {
    
    
   return Future.delayed(Duration(seconds: 2),() => "hi world");
}

login(String s, String t) async {
    
    
  return Future.delayed(Duration(seconds: 2), () => "hi world");
}

In an async function body, the code before await will be executed synchronously and immediately, and the code after await will be blocked until await returns.

Handling errors: To handle asyncerrors in a function, usetry-catch

try {
    
     
  var order = await fetchUserOrder();
} catch (err) {
    
    
  print('Caught error: $err');
}
Future<void> printOrderMessage() async {
    
    
  try {
    
    
    print('Awaiting user order...');
    var order = await fetchUserOrder();
    print(order);
  } catch (err) {
    
    
    print('Caught error: $err');
  }
}

Future<String> fetchUserOrder() = Future.delayed(const Duration(seconds: 2), () => 'Large Latte');

void main() async {
    
    
  await printOrderMessage();
}

Stream

The Future and Stream classes are at the heart of Dart's asynchronous programming.

  • Future represents a computation that does not complete immediately. Unlike ordinary functions that return results directly, asynchronous functions return a Future that will contain the result. This Future will notify the caller when the result is ready.
  • A Stream is a sequence of asynchronous events. It is similar to an asynchronous one Iterable , the difference is that when you get Iterablethe next event to it will give you immediately, but Stream will not give you immediately but tell you when it is ready.

Create Stream

Here is an example of a function that periodically sends integers:

Stream<int> timedCounter(Duration interval, [int? maxCount]) async* {
    
    
  int i = 0;
  while (true) {
    
    
    await Future.delayed(interval);
    yield i++;
    if (i == maxCount) break;
  }
}

The function returns a Stream. The function body Streamstarts running when it should be monitored and continuously generates an incremental number within the specified number range at a certain periodic interval. StreamIf the count parameter is omitted, the loop will execute endlessly, generating more and more numbers unless unsubscribed .

timedCounter()You can use the Stream returned by the function like this :

var counterStream = timedCounter(const Duration(seconds: 1), 15);
counterStream.listen(print); // Print an integer every second, 15 times.

When the listener is canceled ( the method in the object listen()returned by the method is called ), if the next loop body executes to the statement, then the statement acts like the statement. And any statement block executed at this time will cause the function to exit. If the function tries to reset a value before exiting , the attempt will fail with an effect similar to the statement.StreamSubscriptioncancel()yieldreturnfinallyyieldreturn

cancel()The returned by the method completes when the function finally exits Future. If the function exited due to an error, it Futurewill complete with the corresponding error, otherwise it will carry one null.

Alternatively, a more useful example is a function that converts a sequence of Futures into a Stream :

Stream<T> streamFromFutures<T>(Iterable<Future<T>> futures) async* {
    
    
  for (final future in futures) {
    
    
    var result = await future;
    yield result;
  }
}

This function loops to request a Future from the Future sequence and waits for the Future to complete obtaining its result before submitting it to the Stream. If a Future completes with an error, the error is also committed to the Stream.

In practical applications, it is rare to construct a Stream from zero through an async* function. async* functions usually create a Stream from some data source, which is often another Stream. For example, like the Future sequence in the above example, its data often comes from other asynchronous event sources. However, in many cases, asynchronous functions are too simple to handle multiple data sources easily. And that's where the StreamController class comes in.

Another way to convert a Future to a Stream is via Stream.fromFutures()the method:

  Stream.fromFutures([
    // 1秒后返回结果
    Future.delayed(Duration(seconds: 1), () {
    
    
      return "hello 1";
    }),
    // 抛出一个异常
    Future.delayed(Duration(seconds: 2),(){
    
    
      throw AssertionError("Error");
    }),
    // 3秒后返回结果
    Future.delayed(Duration(seconds: 3), () {
    
    
      return "hello 3";
    })
  ]).listen((data){
    
    
    print(data);
  }, onError: (e){
    
    
    print(e.message);
  },onDone: (){
    
    

  });

Use StreamController

If the events of your Stream come not only from the Stream and Future that can be traversed by asynchronous functions, but also from different parts of your program, it will be difficult to generate Stream using the above two methods. Faced with this situation, we can use a StreamController to create and fill the Stream.

StreamController can generate a Stream for you and provide methods to add events to the Stream anytime and anywhere. This Stream has all the logic needed to handle listeners and pauses. The controller object you can handle yourself and just return the Stream required by the caller.

The version of timedCounter() implemented in the code below (from stream_controller.dart) implements the pause functionality by using the onListen, onPause, onResume, and onCancel callbacks in the StreamController.

Stream<int> timedCounter(Duration interval, [int? maxCount]) {
    
    
  late StreamController<int> controller;
  Timer? timer;
  int counter = 0;

  void tick(_) {
    
    
    counter++;
    controller.add(counter); // Ask stream to send counter values as event.
    if (counter == maxCount) {
    
    
      timer?.cancel();
      controller.close(); // Ask stream to shut down and tell listeners.
    }
  }

  void startTimer() {
    
    
    timer = Timer.periodic(interval, tick);
  }

  void stopTimer() {
    
    
    timer?.cancel();
    timer = null;
  }

  controller = StreamController<int>(
      onListen: startTimer,
      onPause: stopTimer,
      onResume: startTimer,
      onCancel: stopTimer);

  return controller.stream;
}

We change the code using Stream as follows:

void listenWithPause() {
    
    
  var counterStream = timedCounter(const Duration(seconds: 1), 15);
  late StreamSubscription<int> subscription;

  subscription = counterStream.listen((int counter) {
    
    
    print(counter); // Print an integer every second.
    if (counter == 5) {
    
    
      // After 5 ticks, pause for five seconds, then resume.
      subscription.pause(Future.delayed(const Duration(seconds: 5)));
    }
  });
}

Use the above timedCounter function in the listenWithPause() function, after running you can see that when the subscription is paused, the count of the printout will also pause, and then resume correctly.

You must use all callbacks onListen, onCancel, onPause and onResume to notify the pause state change, otherwise only onListen or onCancel callback will be called if subscription state and pause state both changed at the same time.

You need to be very careful when using StreamController, and it is prone to problems. For more information about StreamController, please refer to: Using Stream in Dart

Receive Stream events

Like using a for loop to iterate an Iterable, we can use an asynchronous for loop (usually we call it directly await for) to iterate the events in the Stream. For example:

insert image description here
The code simply takes each event in the stream of integer events, adds them together, and returns (wrapped by Future) the added integer value. When the loop body ends, the function pauses until the next event arrives or the Stream completes.

await forFunctions that use loops internally need to be asyncmarked with the keyword.

The following example uses async* functions to generate a simple stream of integers to test the previous code snippet:

Future<int> sumStream(Stream<int> stream) async {
    
    
  var sum = 0;
  await for (final value in stream) {
    
    
    sum += value;
  }
  return sum;
}

Stream<int> countStream(int to) async* {
    
    
  for (int i = 1; i <= to; i++) {
    
    
    yield i;
  }
}

void main() async {
    
    
  var stream = countStream(10);
  var sum = await sumStream(stream);
  print(sum); // 55
}

error event

When the Stream has no more events to be processed, it will become completed. At the same time, the caller can receive the Stream completed event callback just like receiving a new event callback. When reading events with an await for loop, the loop stops when the Stream completes.

Sometimes an error occurs before the Stream is complete; for example, a network request fails when fetching a file from a remote server, or a bug occurs when creating an event. Although errors are always possible, users should be notified when they occur.

Streams can provide error events as well as data events. Most Streams will stop after the first error, but it is also possible to provide multiple errors and continue to provide data events after an error occurs. In this document we only discuss the case where a Stream occurs at most and provides an error event.

When using await for to read the Stream, if an error occurs, it will be thrown by the loop statement, and the loop ends at the same time. You can try-catchcatch errors using the statement. The following example throws an error when the loop iterates until the parameter value equals 4:

Future<int> sumStream(Stream<int> stream) async {
    
    
  var sum = 0;
  try {
    
    
    await for (final value in stream) {
    
    
      sum += value;
    }
  } catch (e) {
    
    
    return -1;
  }
  return sum;
}

Stream<int> countStream(int to) async* {
    
    
  for (int i = 1; i <= to; i++) {
    
    
    if (i == 4) {
    
    
      throw Exception('Intentional exception');
    } else {
    
    
      yield i;
    }
  }
}

void main() async {
    
    
  var stream = countStream(10);
  var sum = await sumStream(stream);
  print(sum); // -1
}

Use of Streams

The Stream class contains many Iterablehelper methods like the class to help you implement some common operations. For example, you can use the method in the Stream API lastWhere()to find out the last positive integer from a Stream.

Future<int> lastPositive(Stream<int> stream) =>
    stream.lastWhere((x) => x >= 0);

Two types of Stream

Stream of type Single-Subscription

The most common type is a Stream that contains only one of some sequence of events. And these events need to be provided in order and cannot be lost. This type of Stream is used when you read a file or receive a web page request.

This kind of Stream can only be set to monitor once. Repeated settings will lose the original event, and the remaining other events you monitor will be meaningless. When you start listening, data will be provided and retrieved in chunks.

Stream of type Broadcast

Another stream is for single messages, which can process one message at a time. For example it can be used for browser mouse events.

You can listen to this Stream at any time, and after that you can get any triggered events. This kind of stream can set multiple different listeners to listen at the same time, and you can also start listening to it again after canceling the previous subscription.

Methods for handling Streams

The methods in the following Stream<T>classes can process the Stream and return the result:

Future<T> get first;
Future<bool> get isEmpty;
Future<T> get last;
Future<int> get length;
Future<T> get single;
Future<bool> any(bool Function(T element) test);
Future<bool> contains(Object? needle);
Future<E> drain<E>([E? futureValue]);
Future<T> elementAt(int index);
Future<bool> every(bool Function(T element) test);
Future<T> firstWhere(bool Function(T element) test, {
    
    T Function()? orElse});
Future<S> fold<S>(S initialValue, S Function(S previous, T element) combine);
Future forEach(void Function(T element) action);
Future<String> join([String separator = '']);
Future<T> lastWhere(bool Function(T element) test, {
    
    T Function()? orElse});
Future pipe(StreamConsumer<T> streamConsumer);
Future<T> reduce(T Function(T previous, T element) combine);
Future<T> singleWhere(bool Function(T element) test, {
    
    T Function()? orElse});
Future<List<T>> toList();
Future<Set<T>> toSet();

All of the above methods, except drain()the and pipe()method, Iterablehave corresponding similar methods in the class. If you're using await fora loop inside an async function (or just inside another method), it's much easier to use the methods above. For example, some code implementations might look like this:

Future<bool> contains(Object? needle) async {
    
    
  await for (final event in this) {
    
    
    if (event == needle) return true;
  }
  return false;
}

Future forEach(void Function(T element) action) async {
    
    
  await for (final event in this) {
    
    
    action(event);
  }
}

Future<List<T>> toList() async {
    
    
  final result = <T>[];
  await forEach(result.add);
  return result;
}

Future<String> join([String separator = '']) async =>
    (await toList()).join(separator);

Convert an existing Stream

The following methods can process the original Stream and return a new Stream. When these methods are called, the listener set on the original Stream will first listen to the converted new Stream, and will turn back to listen to the original Stream after the processing of the new Stream is completed.

Stream<R> cast<R>();
Stream<S> expand<S>(Iterable<S> Function(T element) convert);
Stream<S> map<S>(S Function(T event) convert);
Stream<T> skip(int count);
Stream<T> skipWhile(bool Function(T element) test);
Stream<T> take(int count);
Stream<T> takeWhile(bool Function(T element) test);
Stream<T> where(bool Function(T event) test);

There are also methods in the Iterable class that transform one iterable into another iterable, and these methods above are similar to those in the Iterable class. If you use await for loop in async function, it will be easier to use these methods above.

You can use the conversion class methods provided by the Stream class, such as map()、where()、expand()and take()to deal with most common conversion needs.

For example, suppose you have a counterStreamStream named that prints out an incrementing integer every second. Its implementation process may be as follows:

var counterStream =
    Stream<int>.periodic(const Duration(seconds: 1), (x) => x).take(15);

You can quickly view events using the following code:

counterStream.forEach(print); // Print an integer every second, 15 times.

map()You can call a similar conversion method to convert the Stream's events before listening to the Stream . This method will return a new Stream.

// Double the integer in each event.
var doubleCounterStream = counterStream.map((int x) => x * 2);
doubleCounterStream.forEach(print);

You can use any other transformation method instead map(), such as the following:

.where((int x) => x.isEven) // Retain only even integer events.
.expand((var x) => [x, x]) // Duplicate each event.
.take(5) // Stop after the first five events.

Generally speaking, using various conversion methods is enough to meet your simple usage needs. However, if you need more control over the conversion, you can transform()specify one using the Stream class's method StreamTransformer. The Dart platform library provides Stream converters for many common task needs. For example, the code below uses the and converters dart:convertprovided by the library .utf8.decoderLineSplitter

Stream<List<int>> content = File('someFile.txt').openRead();
List<String> lines = await content
    .transform(utf8.decoder)
    .transform(const LineSplitter())
    .toList();

The following code example reads a file and performs two transformations on its Stream. The first conversion is to convert the file data into UTF-8 encoding format, and then transform the converted data into a LineSplitter for execution. All lines in the file except the lines beginning with # will be printed.

import 'dart:convert';
import 'dart:io';

void main(List<String> args) async {
    
    
  var file = File(args[0]);
  var lines = utf8.decoder
      .bind(file.openRead())
      .transform(const LineSplitter());
  await for (final line in lines) {
    
    
    if (!line.startsWith('#')) print(line);
  }
}

listen() method

This is a "bottom" method against which all other Stream methods are listen()defined.

StreamSubscription<T> listen(void Function(T event)? onData,
    {
    
    Function? onError, void Function()? onDone, bool? cancelOnError});

You just need to inherit the Stream class and implement the listen() method to create a subclass of the Stream type. All other methods in the Stream class depend on listen()calling the method.

The listen() method allows you to monitor a Stream. Until you listen to a Stream, it's just a lazy object that describes the events you want to watch. When you listen to it, it will return a StreamSubscription object, which is used to represent an active Stream that produces events. This is similar to the implementation of the Iterable object, except that the Iterable object can return an iterator and perform actual iteration operations.

Stream allows you to pause, resume or even completely cancel a subscription. You can also set a callback for it, which will notify the caller on every data event, error event, and when the Stream itself is closed.

Microtask micro task

Dart is a single-threaded model , which will continuously fetch tasks from the two queues of the event queue and the microtask queue .

  //执行一个微任务  
  scheduleMicrotask(() {
    
    
    print('a microtask');
  });

Dart VM model

insert image description here
Each isolatezone is isolated, and each isolatezone consists of a Mutator Thread + N Helper Threads (processing GC)
insert image description here

Dart event loop mechanism

Similar to the message loop in Android Handler, MessageQueueDart is also event loopdriven by it. event loopIt will continuously obtain the message events sent by the isolate from the message queue to process. Unlike Android, which has only one Looper+one message queue for one thread, there are two queues in Dart: one is called event queue (event queue), and one is called event queue . Called microtask queue (microtask queue) . The latter has higher priority.

  • Event queue (event queue) , including all external events: information transfer between I/O, mouse events, drawing events, timers, and isolate.
  • Microtask queue (microtask queue) represents an asynchronous task that will be completed in a short time. It has the highest priority and is higher than the event queue. As long as there are tasks in the queue, it can always occupy the event loop. The tasks added by the microtask queue are mainly generated internally by Dart.

insert image description here

After Dart executes main()the method, Event-Loop will execute the events in the two task queues. In each event loop, it always checks whether there are executable tasks in the micro-task queue , and executes them sequentially if there are any. Event, and then the Event execution of the event queue will be queried , and when the Event of the event queue is executed, each time an Event is executed, the microtask queue will be checked again . Therefore, the microtask queue has a higher priority and can be used to jump in the queue.

Because the microtask queue has a higher priority than the event queue, if the microtask queue has too many microtasks, it may occupy the current event loop. As a result, external events such as touch and drawing in the event queue are blocked and stuck.

Guess you like

Origin blog.csdn.net/lyabc123456/article/details/130691091