Flutter Basics
Update 2023/03/18
Tips: regex replace workaround for mdx:
Replace: ```((.|\n)*?)```
With: ```jsx title="Code"$1```
初探Flutter
Flutter 安装
如果是mac系统的话,要先装个xcode
然后根据
安装dart
教程:What Is Flutter? | Learn Flutter and Dart to Build iOS and Android Apps (2022) (oreilly.com)
Flutter是啥?
Flutter就是一种工具,一种能够用同一份代码构建不同环境的原生App的工具。
Dart 是啥?
404开发,专门用来开发前端UI的语言
Flutter 简介
Flutter是基于Dart编写的一个框架。
组件是树状结构的(目前看来好像和Html树有点像)
同时,Flutter是完全用code控制组件的,没有DnD,没有UI editor。Flutter的sdk会把dart code编译为平台的Native code。
并且,这个编译是不使用Platform Primitives的,也就是Qt、ReactNative使用的跨平台方式,而是直接控制绘图。这样可以最大限度保证各个平台上的ui是一致的。
Flutter vs React Native vs Ionic
Dependency
flutter packages get
获取pubspec.yaml里的依赖
Dart 语言
变量 Variables
这下面几个都是等价的:
var name = 'Bob';
String name = 'Bob';
var里面存了一个对String的实例"Bob"的引用。如果要赋值其他类型,就不可以。这个其实也很好理解,因为dart最终是需要编译成下游app所使用的语言的,如android使用的Java,苹果使用的Swift,Windows使用的C++,Linux使用的C等。这些下游的语言几乎都是静态类型的(包括Swift),因此Dart也很难具备类似JS的动态类型的特性。
如果是没有null-safety(在dart 3中即将淘汰),那么变量的默认值就是null。否则必须赋一个初始值,不能为空。
如果需要bypass这个非空检查,那么就在前面加上late
。
Final
Final和Const有什么区别?这两个好像都是初始化后就无法修改了。按照dart的官方文档,Instance的变量不能是const,只能是final。否则会报下面的错:
Only static fields can be declared as const.
Try declaring the field as final, or adding the keyword 'static'.
因为实例是运行时创建的,因此不可能是静态的变量。(否则就是类变量了,类变量可以为const)
class Point {
final double x;
final double y;
static const String name="Point";
Point(this.x, this.y, String otherParam) {}
}
这里如果定义为了final,且没有初始化,那么就要在构造函数里加上。如果已经像const那样初始化过了,就不加。
Collection
List
var list = [1, 2, 3];
Print
这里的list相当于List<int>
类型。这里的List
默认是支持打印的,但是Object是不支持的。
void main() {
List<int> list = [1, 2, 3];
print(list);
print(Point(list[0].toDouble(), list[1].toDouble()));
}
// Console
❯ dart demo.dart
[1, 2, 3]
Instance of 'Point'
给Object 加上ToString之后,就能正常打印了:
class Point {
final double x;
final double y;
static const String name = "Point";
String toString() {
return 'x:${x}, y:${y}';
}
Point(this.x, this.y) {}
}
// Console
❯ dart demo.dart
[1, 2, 3]
x:1.0, y:2.0
Const 不能赋值
下面的代码在js里可以运行:
const a = [1, 3, 4]
a[0] = 3
console.log(a)
❯ node demo.js [ 3, 3, 4 ]
但是在dart中,是不支持这样的。
var constantList = const [1, 2, 3];
// constantList[1] = 1; // This line will cause an error.
Spread operators
在赋值的时候非常好用。
var list = [1, 2, 3];
var list2 = [0, ...list];
assert(list2.length == 4);
还可以通过加上?来避免冗长的非空判断。
var list2 = [0, ...?list];
assert(list2.length == 1);
注意,以上的assert
都只对debug模式下生效,生产环境是无效的。
Collection if, collection for
Collection if:
var nav = ['Home', 'Furniture', 'Plants', if (promoActive) 'Outlet'];
Collection for:
var listOfInts = [1, 2, 3];
var listOfStrings = ['#0', for (var i in listOfInts) '#$i'];
assert(listOfStrings[1] == '#1');
Sets
集合类型,同样支持collection if
List<int> list = [4, 5, 6];
var halogens = {1, 2, 3, 1, for (var i in list) i};
print(halogens);
❯ dart demo.dart {1, 2, 3, 4, 5, 6}
也支持 spread :
print([...halogens]);
❯ dart demo.dart [1, 2, 3, 4, 5, 6]
初始化
var names = <String>{};
// Set<String> names = {}; // This works, too.
// var names = {}; // Creates a map, not a set.
添加元素
var elements = <String>{};
elements.add('fluorine');
elements.addAll(halogens);
Map
var gifts = {
// Key: Value
'first': 'partridge',
'second': 'turtledoves',
'fifth': 'golden rings'
};
var nobleGases = {
2: 'helium',
10: 'neon',
18: 'argon',
};
Typedef
typedef Compare<T> = int Function(T a, T b);
int sort(int a, int b) => a - b;
void main() {
assert(sort is Compare<int>); // True!
}
用xx is type就可以很方便地判断类型。
Generics
泛型最常见的就是前面提到的Collection类型。
值得注意的是,flutter中的泛型是reified(具象化)的,在运行时也携带着这些类型信息。
而Java的泛型是erasure(擦除)的,在运行时不携带信息。因此在Java中,你能判断一个对象是不是列表,但无法判断是否是List\<String>。在flutter中是可以判断的:
var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true
Type System
Flutter有非常强大的static analyzer,可以对代码做静态类型检查。与此同时,由于之前提到的reified generics,Flutter也可以做运行时检查。
下面的代码就会报runtime error:
void main() {
List<Animal> animals = [Dog()];
List<Cat> cats = animals as List<Cat>;
}
Type Inference
当分析器无法得出变量的类型时,默认都是dynamic
类型。
同样,初始化时的变量类型会决定之后的赋值是否合法。
Function
在Dart中,function也是一个对象Function。https://api.dart.dev/stable/2.19.5/dart-core/Function-class.html
几个takeaway:
- Function可以省略类型,实际上是让编译器推测类型
- => 箭头函数作为{ return expr; }的简写
parameters
Named parameters
paramName: value 的形式,都是默认可省略的,除非添加required。与此同时,如果不赋默认值,就是null。
required parameter也是可以为空的:
const Scrollbar({super.key, required Widget? child});
匿名函数
形如
([[Type] param1[, …]]) {
codeBlock;
};
如:
const list = ['apples', 'bananas', 'oranges'];
list.map((item) {
return item.toUpperCase();
}).forEach((item) {
print('$item: ${item.length}');
});
Error handling
try {
// ···
} on Exception catch (e) {
print('Exception details:\n $e');
} catch (e, s) {
print('Exception details:\n $e');
print('Stack trace:\n $s');
}
这其中,S是StackTrace。
void misbehave() {
try {
dynamic foo = true;
print(foo++); // Runtime error
} catch (e) {
print('misbehave() partially handled ${e.runtimeType}.');
rethrow; // Allow callers to see the exception.
}
}
同样,Dart也支持rethrow.
Classes & Objects
这里只写一部分我觉得比较值得记的:
Getter和Setter, @override等和java比较类似。
Extend class
extend就是创建子类。
如果需要重写父类的方法,就加上@override装饰器。
Extension
往已有的类(如库等)加上自定义的方法
extension NumberParsing on String {
int parseInt() {
return int.parse(this);
}
// ···
}
就可以用'42'.parseInt()
了
Mixins
很有意思的一个特性,虽然感觉其实用的地方不多。这个算一个语法糖吧,我个人还是更倾向多个子类去继承父类,而不是用Mixin
class Musician extends Performer with Musical {
// ···
}
class Maestro extends Person with Musical, Aggressive, Demented {
Maestro(String maestroName) {
name = maestroName;
canConduct = true;
}
}
mixin Musical {
bool canPlayPiano = false;
bool canCompose = false;
bool canConduct = false;
void entertainMe() {
if (canPlayPiano) {
print('Playing piano');
} else if (canConduct) {
print('Waving hands');
} else {
print('Humming to self');
}
}
}
Mixin简而言之就很类似于一些条件耦合的代码,如switch case等,只不过是通过修改类的一些属性来展示出完全不同的行为。
Async
经典的async await,但是和单路复用/线程池有区别,下面Isolate里会详细说。而Dart中async返回类型是Future。
await for loop
await for (final request in requestServer) {
handleRequest(request);
}
Isolate
每个isolate都具有自己的内存堆,其他的isolate无法访问。因此Dart中就没有mutex和lock。
Event loop
这里的Event loop是这样:主线程执行某些任务,然后执行完之后会不断搜索需要响应的事件。
和v8的call stack/callback queue非常类似。因此,这种设计也面临一个问题,即每个任务都应该足够小,不然就会阻塞队列。
对于大任务,Dart采取了background isolate的方式,而这两个isolate之间通过Message通信。Send message对信息的内容有要求,具体可以看这个链接
https://api.dart.dev/stable/2.19.5/dart-isolate/SendPort/send.html
一些特性
Sound null safety
在Dart 2.12 Flutter 2.0之后的版本 都支持这个特性。
在null-safe 模式下,变量都是不可以为空的。
// In null-safe Dart, none of these can ever be null.
var i = 42; // Inferred to be an int.
String name = getFileName();
final b = Foo();
如果要显式说明一个变量可能为空,就要加上问号?
int? aNullableInt = null;
项目文件夹
.idea android studio 的配置,其他jb家的ide也有
.vscode vscode 配置
android flutter编译后,会把目标代码注入到这个文件夹
build flutter程序编译后的输出文件夹
ios xcode项目,和android一样flutter编译后,会把目标代码注入到这个文件夹
以上都是passive folder,基本不需要改动
lib 主要的改动都在这里
test 编写程序的自动化测试
特殊文件
.metadata flutter 自动生成的一些信息,一般不需要改动
.package flutter 自动生成的依赖信息,一般不需要改动
<project_name>.iml flutter 自动生成的项目配置信息,不需要改动
.pubspec.yaml 需要改动,管理项目的依赖(项目需要使用哪些第三方的包)
Dart
constructor 可以如上图简写
对于每个Widget, flutter 都会call build(context) 所以build是每个Widget都必须有的。
Arrow function也可以用,但是和js不同的地方在于Dart的Arrow function只能有一行,后面不能跟着花括号。
void main() => runApp(const MyApp());
Widget 分为Layout(invisible)和 Visible, 和React很像
Firebase
Firebase是谷歌提供的一系列云服务,囊括了存储、验证、推送等功能。
安装步骤
Firebase CLI
https://firebase.google.com/docs/cli?authuser=0#mac-linux-auto-script
这里需要把下载下来的二进制文件放到/opt/homebrew/bin里