为了账号安全,请及时绑定邮箱和手机立即绑定

【备战春招】第13天 Flutter中的Future与FutureBuilder

标签:
Android

课程名称Flutter从入门到进阶 实战携程网App 一网打尽核心技术
课程章节:Flutter进阶提升:网络编程与数据存储技术
课程讲师CrazyCodeBoy

课程内容

Future

Future介绍

在 Flutter 中可以借助 Future 实现异步操作。

Future 是个泛型类,可以指定类型。如果没有指定相应类型的话,则Future会使用动态的推导类型。Future<T> 类,表示一个T类型的异步操作结果。如果异步操作不需要结果,则类型为 Future<void>。

Future使用工厂构造函数来创建实例,构成构造函数具有以下特点:

  • 在 Dart 中工厂构造函数的关键字是 factory;
  • 与一般的构造函数不同,工厂构造函数不会自动生成实例,而是通过代码来决定返回的实例;
  • 在构造方法前加上factory 关键字后就变成了工厂构造函数;

Future 有两种状态:

  • pending,表示 Future异步操作正在执行,此时还没有执行结果;
  • completed,执行结束,返回值有两种情况,异步执行的结果和失败的状态。

await 和 async与 Future 配合使用

await和 async 是 Dart支持异步的两个关键字:

  • await,后面会跟着一个 Future,表示等待该异步任务完成后才会继续往下执行。await只能出现在异步函数内部,能够让开发人员可以像写同步代码那样来执行异步任务,而不使用回调的方式。
  • async,修饰的函数是异步的,修饰的函数会返回一个 Future 对象。

示例代码:

test() async {
  int result = await Future.delayed(Duration(milliseconds: 2000), () {
    return Future.value(123);
  });
  print('t3:' + DateTime.now().toString());
  print(result);
}

main() {
  print('t1:' + DateTime.now().toString());
  test();
  print('t2:' + DateTime.now().toString());
}

Future 中的其它方法

Future.then()

Future.then() 接收两个函数类型的参数,第一个参数是异步执行成功的结果回调,第二个参数onError表示异步执行出现异常,返回一个Future对象。

Future<R> then<R>(FutureOr<R> onValue(T value), {Function onError});
Future.catchError()

捕捉 Future 的错误的回调,并且返回一个 Future 对象。

Future.onError()

Future.catchError()回调只处理原始 Future 抛出的错误,不能处理回调函数抛出的错误,此时可以使用Future.onError()。如果catchError()与onError()同时存在,则会只调用onError()。

future.whenComplete()

在 Future 完成之后总是会调用,不管是错误导致的完成还是正常执行完毕,并且返回一个 Future 对象。then().catchError()的模式类似于try-catch,try-catch有个finally代码块,而Future.whenComplete() 相当于Future中的finally。

示例代码:

void main() {
  var random = Random();
  Future.delayed(Duration(seconds: 3), () {
    if (random.nextBool()) {
      return 100;
    } else {
      throw 'boom!';
    }
  }).then(print).catchError(print).whenComplete(() {
    print('done!');
  });
Future.wait()

开发中会遇到这样的场景:网络请求A和网络请求B都完成以后,再执行代码C,此时可以使用Future.wait()。

  • 如果所有 Future 都有正常结果返回。则Future的返回结果是所有指定Future的结果的集合;
  • 如果其中一个或者几个 Future 发生错误,产生了error。则 Future 的返回结果是第一个发生错误的 Future 的值。
future.timeout()

完成一个异步操作可能需要很长的时间,比如:网络请求,但有时我们需要为异步操作设置一个超时时间。

void main() {
  new Future.delayed(new Duration(seconds: 3), () {
    return 1;
  }).timeout(new Duration(seconds: 2)).then(print).catchError(print);
}

运行上述代码会看到:

TimeoutException after 0:00:02.000000: Future not completed。

FutureBuilder

FutureBuilder是一个将异步操作和异步UI更新结合在一起的类,通过FutureBuilder可以将网络请求,数据库读取等的结果更新的页面上。

FutureBuilder的构造方法

FutureBuilder 的构造方法:

FutureBuilder({Key key, Future<T> future, T initialData, @required AsyncWidgetBuilder<T> builder })
  • future,是Future对象表示此构建器当前连接的异步计算;
  • initialData,表示一个非空的Future完成前的初始化数据;
  • builder,是AsyncWidgetBuilder类型的回调函数,是一个基于异步交互构建widget的函数;

AsyncWidgetBuilder类型:

typedef AsyncWidgetBuilder<T> = Widget Function(BuildContext context, AsyncSnapshot<T> snapshot);

其中AsyncWidgetBuilder 函数接受两个参数BuildContext context 与 AsyncSnapshot<T> snapshot,返回一个Widget。

AsyncSnapshot包含异步计算的信息,它具有以下属性:

  • connectionState,枚举类型,表示与异步计算的连接状态,有四个值:none(当前没有连接到任何的异步任务),waiting(连接到异步任务并等待进行交互),active(连接到异步任务并开始交互)和done(异步任务中止);
  • data,异步计算接收的最新数据;
  • error,异步计算接收的最新错误对象;

AsyncSnapshot还具有hasData和hasError属性,以分别检查它是否包含非空数据值或错误值。

FutureBuilder的使用

使用FutureBuilder的基本模式,即在创建新的FutureBuilder对象时,将Future对象作为要处理的异步计算传递。 在构建器函数中,检查connectionState的值,并使用AsyncSnapshot中的数据或错误返回不同的窗口小部件。

class _MyAppState extends State<MyApp> {
  String showResult = '';

  Future<CommonModel> fetchPost() async {
    final response = await http
        .get('https://www.devio.org/io/flutter_app/json/test_common_model.json');
    Utf8Decoder utf8decoder = Utf8Decoder(); //fix 中文乱码
    var result = json.decode(utf8decoder.convert(response.bodyBytes));
    return CommonModel.fromJson(result);
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Future与FutureBuilder实用技巧'),
        ),
        body: FutureBuilder<CommonModel>(
            future: fetchPost(),
            builder:
                (BuildContext context, AsyncSnapshot<CommonModel> snapshot) {
              switch (snapshot.connectionState) {
                case ConnectionState.none:
                  return new Text('Input a URL to start');
                case ConnectionState.waiting:
                  return new Center(child: new CircularProgressIndicator());
                case ConnectionState.active:
                  return new Text('');
                case ConnectionState.done:
                  if (snapshot.hasError) {
                    return new Text(
                      '${snapshot.error}',
                      style: TextStyle(color: Colors.red),
                    );
                  } else {
                    return new Column(children: <Widget>[
                      Text('icon:${snapshot.data.icon}'),
                      Text('statusBarColor:${snapshot.data.statusBarColor}'),
                      Text('title:${snapshot.data.title}'),
                      Text('url:${snapshot.data.url}')
                    ]);
                  }
              }
            }),
      ),
    );
  }
}

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

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

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

课程总结

由于StatefulWidget 会维护一个 State,当State有变动的时候会调用 didUpdateWidget()方法,重新build()。

在使用FutureBuilder时,future这个参数建议在 initState() 里初始化,不要在 build() 方法里初始化,否则会一直 rebuild()。

didUpdateWidget() 方法的源码:

@override
void didUpdateWidget(FutureBuilder<T> oldWidget) {
  super.didUpdateWidget(oldWidget);
  if (oldWidget.future != widget.future) {
    if (_activeCallbackIdentity != null) {
      _unsubscribe();
      _snapshot = _snapshot.inState(ConnectionState.none);
    }
    _subscribe();
  }
}

可以看出来方法中会判断future 这个字段,然后会重复调用 inState(),重复进行rebuild()。所以一定不要在 build 方法里初始化 future 参数。
图片描述

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
移动开发工程师
手记
粉丝
11
获赞与收藏
16

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消