回顾

上一篇我们已经拿到了数据,本章主要讲数据怎么显示。数据显示就不可避免的要提到异步加载以及数据的转换

json转换成字典

�Flutter中json数据转换成字典,可以使用json.decode

  1. import 'dart:convert';
  2. json.decode(response.body);

字典转换成模型

构造方法如果想要有返回值,此时需要使用工厂方法factory

  1. class ChatModel {
  2. final String? imgUrl;
  3. final String? name;
  4. final String? message;
  5. ChatModel({this.imgUrl, this.name, this.message});
  6. factory ChatModel.fromMap(Map map) {
  7. return ChatModel(
  8. imgUrl: map['imgUrl'], name: map['name'], message: map['message']);
  9. }
  10. }

模型数据源

异步加载的数据,拿到所有的数据的返回值,可以使用Future

  1. Future<List<ChatModel>> getData() async {
  2. final url =
  3. Uri.parse('http://rap2api.taobao.org/app/mock/293759/home/chat/list');
  4. var response = await http.get(url);
  5. if (response.statusCode == 200) {
  6. // json转换成字典
  7. final responseBody = json.decode(response.body);
  8. return responseBody['data']
  9. .map<ChatModel>((item) => ChatModel.fromMap(item))
  10. .toList();
  11. //转模型
  12. } else {
  13. throw Exception('statusCode=${response.statusCode}');
  14. }
  15. }
  16. }

渲染

有一个专门用来渲染异步加载回来的数据FutureBuilder,有一个必传参数buildercommand点击进去文档是一个有返回值有参数的方法typedef AsyncWidgetBuilder<T> = Widget Function(BuildContext context, AsyncSnapshot<T> snapshot);

  1. Container(
  2. child: FutureBuilder(
  3. builder: (BuildContext context, AsyncSnapshot snapshot) {
  4. print('${snapshot.data}');
  5. return Container();
  6. },
  7. future: getData(),
  8. )),

打印snapshot.data的时候,发现这里会走两次,一次页面刚加载的时候没有拿到数据这里打印的是null,第二次则是拿到数组之后再次刷新数据
image.png
snapshot.connectionState:waiting的时候没有数据,done的时候表明数据已经加载完了。
image.png
所以此时我们可以根据这个connectionState的状态来判断

  1. if (snapshot.connectionState == ConnectionState.waiting) {
  2. return Center(child: Text('加载中...'));
  3. }

ListView的children可以使用snapshot.data.map<Widget>((ChatModel item)数组遍历生成,同时这里介绍一个新的Widget:ListTile包含比较简单常用的一些布局

  1. const ListTile({
  2. Key? key,
  3. this.leading,
  4. this.title,
  5. this.subtitle,
  6. this.trailing,
  7. this.isThreeLine = false,
  8. this.dense,
  9. this.visualDensity,
  10. this.shape,
  11. this.contentPadding,
  12. this.enabled = true,
  13. this.onTap,
  14. this.onLongPress,
  15. this.mouseCursor,
  16. this.selected = false,
  17. this.focusColor,
  18. this.hoverColor,
  19. this.focusNode,
  20. this.autofocus = false,
  21. this.tileColor,
  22. this.selectedTileColor,
  23. this.enableFeedback,
  24. this.horizontalTitleGap,
  25. this.minVerticalPadding,
  26. this.minLeadingWidth,
  27. }) : assert(isThreeLine != null),
  28. assert(enabled != null),
  29. assert(selected != null),
  30. assert(autofocus != null),
  31. assert(!isThreeLine || subtitle != null),
  32. super(key: key);

这样布局出来的效果就是这样的:
image.png

状态保留

上面有一个小问题,每次页面切出去再次切回来的时候,数据都会重新加载。这样的话状态就没有保留。如果想要保留的话需要:

  1. 继承AutomaticKeepAliveClientMixin

    class _ChatPageState extends State<ChatPage> with AutomaticKeepAliveClientMixin

  2. 然后实现父类的方法

    1. @override
    2. bool get wantKeepAlive => true;

    3.在Widget build(BuildContext context)中实现super.build(context);

修改Widget树

尽管实现了上面的3部曲,但是切回来的时候还是会重新init,这个是因为在根控制器下每次的页面都是重新生成的,并没有在当前的Widget树中,我们回到RootPage页面中重新修改一下

  1. 声明一个final PageController _pageController = PageController();
  2. body使用PageView
    1. body: PageView(
    2. children: _pages,
    3. controller: _pageController,
    4. ),
    3.切换页面的时候,_pageController同步跳转
    1. setState(() {
    2. _currentIndex = index;
    3. _pageController.jumpToPage(_currentIndex);
    4. });
    这样每次点击Tabbar的时候就能保存住当前页面的状态。但是此时有个小问题,当前的页面可以滑动切屏,但是底部的按钮不会随之联动。这个小问题可以在PageViewonPageChanged中解决
    1. onPageChanged: (index) {
    2. setState(() {
    3. _currentIndex = index;
    4. });
    5. },
    或者设置不能拖拽切屏:physics: NeverScrollableScrollPhysics(),

Future异步

在上面网络请求的时候用到了Future,那么这个到底是什么?Fluture是主线程当中的异步代码

  1. 下面打印的顺序是什么: ```dart void main() { testFuture(); print(‘A’); }

void testFuture() async { Future(() { print(‘C’); }); print(‘B’); }

  1. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/21860540/1636943200029-cc0835a3-14a5-437e-a517-1293e2591485.png#clientId=udd5c4aaf-1eee-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=114&id=ucd2e0d3f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=114&originWidth=822&originalType=binary&ratio=1&rotation=0&showTitle=false&size=10040&status=done&style=none&taskId=ubf67a770-9eb7-4557-ac26-274e530703a&title=&width=822)
  2. 2. 加入`await`之后打印的顺序是什么?
  3. ```dart
  4. void main() {
  5. testFuture();
  6. print('A');
  7. }
  8. void testFuture() async {
  9. await Future(() {
  10. print('C');
  11. }).then((value) => print('D'));
  12. print('B');
  13. }

image.png
经过测试发现使用Future修饰的代码块会异步执行,不会卡住当前的线程。如果希望在这个异步任务执行完成之后再操作,需要在Future前面加上一个await。

  1. 多个Future并行的时候的打印顺序: ```dart void main() { testFuture(); print(‘A’); }

void testFuture() async { Future(() { return ‘任务1’; }).then((value) => print(‘$value 执行结束’));

Future(() { return ‘任务2’; }).then((value) => print(‘$value 执行结束’));

Future(() { return ‘任务3’; }).then((value) => print(‘$value 执行结束’));

Future(() { return ‘任务4’; }).then((value) => print(‘$value 执行结束’));

print(‘任务添加完毕’); }

  1. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/21860540/1636943797430-f9988229-d201-4b54-80ce-6ef580fa13d4.png#clientId=udd5c4aaf-1eee-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=135&id=u2b141932&margin=%5Bobject%20Object%5D&name=image.png&originHeight=135&originWidth=776&originalType=binary&ratio=1&rotation=0&showTitle=false&size=11845&status=done&style=none&taskId=uc2fd982e-dcf6-4109-9847-637423c7824&title=&width=776)<br />调整顺序或者是让其中的一个任务加入睡眠等多项测试之后,发现多个Future并行发现这个执行的顺序就是添加的顺序。
  2. 4. 有依赖关系的`Future`打印顺序:
  3. ```dart
  4. void testFuture() async {
  5. Future(() {
  6. return '任务1';
  7. }).then((value) {
  8. print('$value结束');
  9. return '$value任务2';
  10. }).then((value) {
  11. print('$value结束');
  12. return '$value任务3';
  13. }).then((value) {
  14. print('$value结束');
  15. return '$value任务4';
  16. });
  17. }

image.png

  1. 多个Future执行完成之后再操作:

    1. void testFuture() async {
    2. Future.wait([
    3. Future(() {
    4. return '任务1';
    5. }),
    6. Future(() {
    7. return '任务2';
    8. }),
    9. Future(() {
    10. return '任务3';
    11. }),
    12. Future(() {
    13. return '任务4';
    14. }),
    15. ]).then((value) => print('$value')); // wait里面的执行顺序也是添加的顺序
    16. }

    image.png

  2. scheduleMicrotask可以插队到Future任务前面

    1. void testFuture() async {
    2. Future.wait([
    3. Future(() {
    4. return '任务1';
    5. }),
    6. Future(() {
    7. return '任务2';
    8. }),
    9. Future(() {
    10. return '任务3';
    11. }),
    12. Future(() {
    13. return '任务4';
    14. }),
    15. ]).then((value) => print('$value'));
    16. scheduleMicrotask(() {
    17. print('scheduleMicrotask');
    18. });
    19. }

    image.png
    Dart中就只有两种队列:一种是事件队列,一种是微任务队列,微任务队列的优先级始终高于事件队列,没有多线程一说。

  • 事件队列(event queue),包含所有的外来事件:I/O、mouse events、drawing events、timers、isolate之间的信息传递
  • 微任务队列(microtask queue),表示一个短时间内就会完成的异步任务。它的优先级最高,只要队列中还有任务,就可以一直霸占着事件循环。microtask queue添加的任务主要是由Dart内部产生

image.png

  1. 下列打印的顺序是什么? ```dart void testFuture() async { Future x = Future(() => print(‘A’)); Future(() => print(‘B’)); scheduleMicrotask(() { print(‘C’); }); x.then((value) => print(‘D’)); print(‘E’); }
  1. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/21860540/1636947670370-6174f2a3-189d-4bbc-9586-424580950030.png#clientId=udd5c4aaf-1eee-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=113&id=u68dd2b7c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=113&originWidth=791&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4012&status=done&style=none&taskId=u73d05bd3-712e-494a-bbdd-21e78af9a4e&title=&width=791)<br />首先肯定是主线程的`E`,然后是微任务的`C`,剩下的两个`Future`按照添加的顺序执行,首先执行A和D最后是B<br />
  2. 8. Fluture中嵌套微任务的执行顺序:
  3. ```dart
  4. void testFuture() async {
  5. Future(() => print('A')).then((value) {
  6. scheduleMicrotask(() {
  7. print('D');
  8. });
  9. }).then((value) => print('F'));
  10. Future(() => print('B'));
  11. scheduleMicrotask(() {
  12. print('C');
  13. });
  14. print('E');
  15. }

image.png
这里可以这么理解:then后面的代码可以理解为丢到了微任务队列去执行。

补充:使用Flutter的时候尽量使用链式调用,保证在最后调用.catchError来捕获异常

  1. getData() async {
  2. print('开始了');
  3. await Future(() {
  4. for (int i = 0; i < 100; i++) {}
  5. return '循环结束';
  6. })
  7. .then((value) => print('$value'))
  8. .whenComplete(() => print('完成了'))
  9. .catchError((e) => print(e.toString()));
  10. print('await之后的代码');
  11. }

Dart中的多线程

Dart当中的Isolate更像一个进程,有独立的内存空间。意味着每个进程之间的数据是独立的,不存在抢夺空间的情况,所以不需要锁的概念。

  1. void main() {
  2. IsolateDemo();
  3. }
  4. void IsolateDemo() {
  5. print('1');
  6. Isolate.spawn(func, 3);
  7. Isolate.spawn(func, 4);
  8. Isolate.spawn(func, 5);
  9. Isolate.spawn(func, 6);
  10. Isolate.spawn(func, 7);
  11. Isolate.spawn(func, 8);
  12. Isolate.spawn(func, 9);
  13. sleep(Duration(seconds: 3));
  14. print('2');
  15. }

image.png
上面说了不存在同一块内存的问题,我们也来验证一下,在下面的代码中,我在Isolate中修改了a的值,那么最后打印的a=?

  1. int a = 100;
  2. void IsolateDemo() {
  3. print('1');
  4. Isolate.spawn(func, 3);
  5. Isolate.spawn(func, 4);
  6. Isolate.spawn(func, 5);
  7. Isolate.spawn(func, 6);
  8. Isolate.spawn(func, 7);
  9. Isolate.spawn(func, 8);
  10. Isolate.spawn(func, 9);
  11. sleep(Duration(seconds: 3));
  12. print('2');
  13. print('a = $a');
  14. }
  15. func(int count) {
  16. print('$count');
  17. a = count;
  18. }

image.png
经验证,最后a还是等于100.也就是说在Isolate中的修改并没有效果。

ReceivePort & Isolate

  1. void IsolateDemo() {
  2. print('1');
  3. ReceivePort port = ReceivePort();
  4. Isolate.spawn(func, port.sendPort);
  5. port.listen((message) {
  6. print('message=$message');
  7. });
  8. sleep(Duration(seconds: 3));
  9. print('2');
  10. }
  11. func(SendPort port) {
  12. port.send(10);
  13. }

image.png
�上面的代码还是有一点需要完善的地方:此时注意需要手动的关闭端口和销毁Isolate

  1. void IsolateDemo() async {
  2. print('1');
  3. ReceivePort port = ReceivePort();
  4. Isolate iso = await Isolate.spawn(func, port.sendPort);
  5. port.listen((message) {
  6. print('message=$message');
  7. port.close();
  8. iso.kill();
  9. });
  10. sleep(Duration(seconds: 3));
  11. print('2');
  12. }

compute

用法跟Isolate差不多,是在Isolate上的进一步包装。compute不需要手动kill

  1. void main() {
  2. Comouterdemo();
  3. }
  4. void Comouterdemo() {
  5. print('1');
  6. compute(func1, 10);
  7. sleep(Duration(seconds: 3));
  8. print('2');
  9. }
  10. func1(int num) {
  11. print('$num');
  12. }

image.png
搭配await异步使用

  1. void main() {
  2. Comouterdemo();
  3. }
  4. void Comouterdemo() async {
  5. print('1');
  6. int result = await compute(func1, 10);
  7. sleep(Duration(seconds: 3));
  8. print('result=$result');
  9. print('2');
  10. }
  11. int func1(int num) {
  12. print('$num');
  13. return num;
  14. }

image.png

一些小知识

  • 关于import, as关键字就是给库起别名,目的是防止类名、方法名冲突import 'package:http/http.dart' as http;
  • 导入库,默认是整个文件都会导入,如果需要指定的话有两个关键字。show:执行需要导入的内容;hide:需要隐藏的内容
  • pubspec.yamlpublish_to:指定发布到哪里去,默认的都是到pub.dev里面去
  • pubspec.yamlversion: 当前项目的版本号
  • pubspec.yaml中 Dart的版本environment:sdk: ">=2.12.0 <3.0.0"
  • pubspec.yamldev_dependencies: 开发环境依赖的版本打包的时候不会有这些
  • pubspec.yamldependencies: 第三方库导入位置。dio:^4.0.1大版本不变的区间写法,相当于>=4.0.1 <5.0.0; dio:4.0.1指定4.0.1版本; dio:any任意版本