Skip to main content

Flutter Basics

Update 2023/03/18

Tips: regex replace workaround for mdx:

Replace: ```((.|\n)*?)```

With: ```jsx title="Code"$1```

初探Flutter

Flutter 安装

如果是mac系统的话,要先装个xcode

然后根据

Get the Dart SDK | Dart

安装dart

教程:What Is Flutter? | Learn Flutter and Dart to Build iOS and Android Apps (2022) (oreilly.com)

Flutter是啥?

Flutter就是一种工具,一种能够用同一份代码构建不同环境的原生App的工具。

image-20221209195008679

Dart 是啥?

404开发,专门用来开发前端UI的语言

image-20221209195116375

Flutter 简介

Flutter是基于Dart编写的一个框架。

image-20221209195233540

组件是树状结构的(目前看来好像和Html树有点像)

image-20221209195431265

同时,Flutter是完全用code控制组件的,没有DnD,没有UI editor。Flutter的sdk会把dart code编译为平台的Native code。

image-20221209195929742

并且,这个编译是不使用Platform Primitives的,也就是Qt、ReactNative使用的跨平台方式,而是直接控制绘图。这样可以最大限度保证各个平台上的ui是一致的。

image-20221209200122963

Flutter vs React Native vs Ionic

image-20221209233646510

Dependency

image-20221210114702050

flutter packages get

获取pubspec.yaml里的依赖

Dart 语言

变量 Variables

这下面几个都是等价的:

Code
var name = 'Bob';
Code
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。否则会报下面的错:

Code
Only static fields can be declared as const.
Try declaring the field as final, or adding the keyword 'static'.

因为实例是运行时创建的,因此不可能是静态的变量。(否则就是类变量了,类变量可以为const)

Code
class Point {
final double x;
final double y;
static const String name="Point";

Point(this.x, this.y, String otherParam) {}
}

这里如果定义为了final,且没有初始化,那么就要在构造函数里加上。如果已经像const那样初始化过了,就不加。

Collection

List

Code
var list = [1, 2, 3];
Print

这里的list相当于List<int>类型。这里的List默认是支持打印的,但是Object是不支持的。

Code
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之后,就能正常打印了:

Code
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里可以运行:

Code
const a = [1, 3, 4]
a[0] = 3
console.log(a)

❯ node demo.js [ 3, 3, 4 ]

但是在dart中,是不支持这样的。

Code
var constantList = const [1, 2, 3];
// constantList[1] = 1; // This line will cause an error.
Spread operators

在赋值的时候非常好用。

Code
var list = [1, 2, 3];
var list2 = [0, ...list];
assert(list2.length == 4);

还可以通过加上?来避免冗长的非空判断。

Code
var list2 = [0, ...?list];
assert(list2.length == 1);

注意,以上的assert都只对debug模式下生效,生产环境是无效的。

Collection if, collection for

Collection if:

Code
var nav = ['Home', 'Furniture', 'Plants', if (promoActive) 'Outlet'];

Collection for:

Code
var listOfInts = [1, 2, 3];
var listOfStrings = ['#0', for (var i in listOfInts) '#$i'];
assert(listOfStrings[1] == '#1');

Sets

集合类型,同样支持collection if

Code
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 :

Code
print([...halogens]);

❯ dart demo.dart [1, 2, 3, 4, 5, 6]

初始化
Code
var names = <String>{};
// Set<String> names = {}; // This works, too.
// var names = {}; // Creates a map, not a set.
添加元素
Code
var elements = <String>{};
elements.add('fluorine');
elements.addAll(halogens);

Map

Code
var gifts = {
// Key: Value
'first': 'partridge',
'second': 'turtledoves',
'fifth': 'golden rings'
};

var nobleGases = {
2: 'helium',
10: 'neon',
18: 'argon',
};

Typedef

Code
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中是可以判断的:

Code
var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true

Type System

Flutter有非常强大的static analyzer,可以对代码做静态类型检查。与此同时,由于之前提到的reified generics,Flutter也可以做运行时检查。

下面的代码就会报runtime error:

Code
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也是可以为空的:

Code
const Scrollbar({super.key, required Widget? child});

匿名函数

形如

Code
([[Type] param1[,]]) {
codeBlock;
};

如:

Code
const list = ['apples', 'bananas', 'oranges'];
list.map((item) {
return item.toUpperCase();
}).forEach((item) {
print('$item: ${item.length}');
});

Error handling

Code
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。

Code
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

往已有的类(如库等)加上自定义的方法

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

就可以用'42'.parseInt()

Mixins

很有意思的一个特性,虽然感觉其实用的地方不多。这个算一个语法糖吧,我个人还是更倾向多个子类去继承父类,而不是用Mixin

Code
class Musician extends Performer with Musical {
// ···
}

class Maestro extends Person with Musical, Aggressive, Demented {
Maestro(String maestroName) {
name = maestroName;
canConduct = true;
}
}
Code
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

Code
await for (final request in requestServer) {
handleRequest(request);
}

Isolate

每个isolate都具有自己的内存堆,其他的isolate无法访问。因此Dart中就没有mutex和lock。

A more general figure showing that any isolate runs some code, optionally responds to events, and then exits

Event loop

这里的Event loop是这样:主线程执行某些任务,然后执行完之后会不断搜索需要响应的事件。

A figure showing the main isolate executing event handlers, one by one

和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

Sound null safety | Dart

在Dart 2.12 Flutter 2.0之后的版本 都支持这个特性。

在null-safe 模式下,变量都是不可以为空的。

Code
// 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();

如果要显式说明一个变量可能为空,就要加上问号?

Code
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

image-20221210143922535

constructor 可以如上图简写

对于每个Widget, flutter 都会call build(context) 所以build是每个Widget都必须有的。

Arrow function也可以用,但是和js不同的地方在于Dart的Arrow function只能有一行,后面不能跟着花括号。

Code
void main() => runApp(const MyApp());

Widget 分为Layout(invisible)和 Visible, 和React很像

image-20221210180118057

Firebase

image-20230318124942565

Firebase是谷歌提供的一系列云服务,囊括了存储、验证、推送等功能。

安装步骤

Firebase CLI

https://firebase.google.com/docs/cli?authuser=0#mac-linux-auto-script

这里需要把下载下来的二进制文件放到/opt/homebrew/bin里