很多时候我们会依赖一些异步数据来动态更新UI:
- 比如在打开一个页面时我们需要先从互联网上获取数据,在获取数据的过程中我们显示一个加载框,等获取到数据时我们再渲染页面;
- 又比如我们想展示Stream(比如文件流、互联网数据接收流)的进度。
当然,通过StatefulWidget我们完全可以实现上述这些功能。但由于在实际开发中依赖异步数据更新UI的这种场景非常常见,因此Flutter专门提供了
- FutureBuilder
- StreamBuilder
两个组件来快速实现这种功能。
FutureBuilder
FutureBuilder 会依赖一个Future,它会根据所依赖的Future的状态来动态构建自身
我们看一下 FutureBuilder 构造函数:
FutureBuilder({this.future,this.initialData,@required this.builder,})
- future:FutureBuilder依赖的Future,通常是一个异步耗时任务
- initialData:初始数据,用户设置默认数据
builder:Widget构建器;该构建器会在Future执行的不同阶段被多次调用,构建器签名如下:
Function (BuildContext context, AsyncSnapshot snapshot)
- snapshot 会包含当前异步任务的状态信息及结果信息 ,比如我们可以
- 通过snapshot.connectionState 获取异步任务的状态信息
- 通过snapshot.hasError 判断异步任务是否有错误等等,完整的定义读者可以查看AsyncSnapshot类定义
⚠️ Tip:另外,FutureBuilder的builder函数签名和StreamBuilder的builder是相同的
我们实现一个路由,当该路由打开时我们从网上获取数据,获取数据时弹一个加载框;获取结束时,如果成功则显示获取到的数据,如果失败则显示错误。由于我们还没有介绍在flutter中如何发起网络请求,所以在这里我们不真正去网络请求数据,而是模拟一下这个过程,隔3秒后返回一个字符串:
Future<String> mockNetworkData() async {return Future.delayed(Duration(seconds: 2), () => "我是从互联网上获取的数据");}
FutureBuilder使用代码如下:
import 'package:flutter/material.dart';void main() => runApp(new MyApp());class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(debugShowCheckedModeBanner: true,title: '主题测试',theme: ThemeData(primarySwatch: Colors.blue,),home: Scaffold(appBar: AppBar(title: Text("主题测试")),body: TestFutureBuilder(),));}}Future<String> mockNetworkData() async {return Future.delayed(Duration(seconds: 2), () => "我是从互联网上获取的数据");}class TestFutureBuilder extends StatefulWidget {TestFutureBuilder({Key key}) : super(key: key);@overrideTest_FutureBuilderState createState() => Test_FutureBuilderState();}class Test_FutureBuilderState extends State<TestFutureBuilder> {@overrideWidget build(BuildContext context) {return Center(child: FutureBuilder<String>(future: mockNetworkData(),builder: (BuildContext context, AsyncSnapshot snapshot) {// 请求已结束if (snapshot.connectionState == ConnectionState.done) {if (snapshot.hasError) {// 请求失败,显示错误return Text("Error: ${snapshot.error}");} else {// 请求成功,显示数据return Text("Contents: ${snapshot.data}");}} else {// 请求未结束,显示loadingreturn CircularProgressIndicator();}},),);}}
运行结果如图所示:

上面代码中我们在builder中根据当前异步任务状态ConnectionState来返回不同的widget。ConnectionState是一个枚举类,定义如下:
enum ConnectionState {none, /// 当前没有异步任务,比如[FutureBuilder]的[future]为null时waiting, /// 异步任务处于等待状态active, /// Stream处于激活状态(流上已经有数据传递了),对于FutureBuilder没有该状态done, /// 异步任务已经终止}
注意,ConnectionState.active只在StreamBuilder中才会出现
StreamBuilder
我们知道,在Dart中 Stream 也是用于接收异步事件数据,和Future 不同的是,
它可以接收多个异步操作的结果,它常用于会多次读取数据的异步任务场景,如网络内容下载、文件读写等。StreamBuilder正是用于配合Stream来展示流上事件(数据)变化的UI组件。
下面看一下StreamBuilder的默认构造函数:
StreamBuilder({Key key,this.initialData,Stream<T> stream,@required this.builder,})
可以看到和FutureBuilder的构造函数只有一点不同:前者需要一个future,而后者需要一个stream
我们创建一个计时器的示例:每隔1秒,计数加1。这里,我们使用Stream来实现每隔一秒生成一个数字:
Stream<int> counter() {//创建以[周期]间隔重复发出事件的流。return Stream.periodic(Duration(milliseconds: 1), (i) {return i;});}
StreamBuilder使用代码如下:
import 'package:flutter/material.dart';void main() => runApp(new MyApp());class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(debugShowCheckedModeBanner: true,title: 'StreamBuilder',theme: ThemeData(primarySwatch: Colors.blue,),home: Scaffold(appBar: AppBar(title: Text("StreamBuilder")),body: TestFutureBuilder(),));}}Stream<int> counter() {//创建以[周期]间隔重复发出事件的流。return Stream.periodic(Duration(milliseconds: 1), (i) {return i;});}class TestFutureBuilder extends StatefulWidget {TestFutureBuilder({Key key}) : super(key: key);@overrideTest_FutureBuilderState createState() => Test_FutureBuilderState();}class Test_FutureBuilderState extends State<TestFutureBuilder> {@overrideWidget build(BuildContext context) {return StreamBuilder<int>(stream: counter(), ////initialData: ,// a Stream<int> or nullbuilder: (BuildContext context, AsyncSnapshot<int> snapshot) {if (snapshot.hasError)return Text('Error: ${snapshot.error}');switch (snapshot.connectionState) {case ConnectionState.none:return Text('没有Stream');case ConnectionState.waiting:return Text('等待数据...');case ConnectionState.active:return Text('active: ${snapshot.data}');case ConnectionState.done:return Text('Stream已关闭');}return null; // unreachable},);}}
读者可以自己运行本示例查看运行结果。注意,本示例只是为了演示StreamBuilder的使用,在实战中,凡是UI会依赖多个异步数据而发生变化的场景都可以使用StreamBuilder
FutureBuilder和StreamBuilder源码
import 'dart:async' show StreamSubscription;import 'package:flutter/foundation.dart';import 'framework.dart';abstract class StreamBuilderBase<T, S> extends StatefulWidget {const StreamBuilderBase({ Key? key, this.stream }) : super(key: key);final Stream<T>? stream;S initial();S afterConnected(S current) => current;S afterData(S current, T data);S afterError(S current, Object error, StackTrace stackTrace) => current;S afterDone(S current) => current;S afterDisconnected(S current) => current;Widget build(BuildContext context, S currentSummary);@overrideState<StreamBuilderBase<T, S>> createState() => _StreamBuilderBaseState<T, S>();}class _StreamBuilderBaseState<T, S> extends State<StreamBuilderBase<T, S>> {StreamSubscription<T>? _subscription; // ignore: cancel_subscriptionslate S _summary;@overridevoid initState() {super.initState();_summary = widget.initial();_subscribe();}@overridevoid didUpdateWidget(StreamBuilderBase<T, S> oldWidget) {super.didUpdateWidget(oldWidget);if (oldWidget.stream != widget.stream) {if (_subscription != null) {_unsubscribe();_summary = widget.afterDisconnected(_summary);}_subscribe();}}@overrideWidget build(BuildContext context) => widget.build(context, _summary);@overridevoid dispose() {_unsubscribe();super.dispose();}void _subscribe() {if (widget.stream != null) {_subscription = widget.stream!.listen((T data) {setState(() {_summary = widget.afterData(_summary, data);});}, onError: (Object error, StackTrace stackTrace) {setState(() {_summary = widget.afterError(_summary, error, stackTrace);});}, onDone: () {setState(() {_summary = widget.afterDone(_summary);});});_summary = widget.afterConnected(_summary);}}void _unsubscribe() {if (_subscription != null) {_subscription!.cancel();_subscription = null;}}}enum ConnectionState {none,waiting,active,done,}@immutableclass AsyncSnapshot<T> {const AsyncSnapshot._(this.connectionState, this.data, this.error, this.stackTrace): assert(connectionState != null),assert(!(data != null && error != null)),assert(stackTrace == null || error != null);const AsyncSnapshot.nothing() : this._(ConnectionState.none, null, null, null);const AsyncSnapshot.waiting() : this._(ConnectionState.waiting, null, null, null);const AsyncSnapshot.withData(ConnectionState state, T data): this._(state, data, null, null);const AsyncSnapshot.withError(ConnectionState state,Object error, [StackTrace stackTrace = StackTrace.empty,]) : this._(state, null, error, stackTrace);final ConnectionState connectionState;final T? data;T get requireData {if (hasData)return data!;if (hasError)throw error!;throw StateError('Snapshot has neither data nor error');}final Object? error;final StackTrace? stackTrace;AsyncSnapshot<T> inState(ConnectionState state) => AsyncSnapshot<T>._(state, data, error, stackTrace);bool get hasData => data != null;bool get hasError => error != null;@overrideString toString() => '${objectRuntimeType(this, 'AsyncSnapshot')}($connectionState, $data, $error, $stackTrace)';@overridebool operator ==(Object other) {if (identical(this, other))return true;return other is AsyncSnapshot<T>&& other.connectionState == connectionState&& other.data == data&& other.error == error&& other.stackTrace == stackTrace;}@overrideint get hashCode => hashValues(connectionState, data, error);}typedef AsyncWidgetBuilder<T> = Widget Function(BuildContext context, AsyncSnapshot<T> snapshot);class StreamBuilder<T> extends StreamBuilderBase<T, AsyncSnapshot<T>> {const StreamBuilder({Key? key,this.initialData,Stream<T>? stream,required this.builder,}) : assert(builder != null),super(key: key, stream: stream);final AsyncWidgetBuilder<T> builder;final T? initialData;@overrideAsyncSnapshot<T> initial() => initialData == null? AsyncSnapshot<T>.nothing(): AsyncSnapshot<T>.withData(ConnectionState.none, initialData as T);@overrideAsyncSnapshot<T> afterConnected(AsyncSnapshot<T> current) => current.inState(ConnectionState.waiting);@overrideAsyncSnapshot<T> afterData(AsyncSnapshot<T> current, T data) {return AsyncSnapshot<T>.withData(ConnectionState.active, data);}@overrideAsyncSnapshot<T> afterError(AsyncSnapshot<T> current, Object error, StackTrace stackTrace) {return AsyncSnapshot<T>.withError(ConnectionState.active, error, stackTrace);}@overrideAsyncSnapshot<T> afterDone(AsyncSnapshot<T> current) => current.inState(ConnectionState.done);@overrideAsyncSnapshot<T> afterDisconnected(AsyncSnapshot<T> current) => current.inState(ConnectionState.none);@overrideWidget build(BuildContext context, AsyncSnapshot<T> currentSummary) => builder(context, currentSummary);}// TODO(ianh): remove unreachable code above once https://github.com/dart-lang/sdk/issues/35520 is fixedclass FutureBuilder<T> extends StatefulWidget {const FutureBuilder({Key? key,this.future,this.initialData,required this.builder,}) : assert(builder != null),super(key: key);final Future<T>? future;final AsyncWidgetBuilder<T> builder;final T? initialData;static bool debugRethrowError = false;@overrideState<FutureBuilder<T>> createState() => _FutureBuilderState<T>();}class _FutureBuilderState<T> extends State<FutureBuilder<T>> {Object? _activeCallbackIdentity;late AsyncSnapshot<T> _snapshot;@overridevoid initState() {super.initState();_snapshot = widget.initialData == null? AsyncSnapshot<T>.nothing(): AsyncSnapshot<T>.withData(ConnectionState.none, widget.initialData as T);_subscribe();}@overridevoid didUpdateWidget(FutureBuilder<T> oldWidget) {super.didUpdateWidget(oldWidget);if (oldWidget.future != widget.future) {if (_activeCallbackIdentity != null) {_unsubscribe();_snapshot = _snapshot.inState(ConnectionState.none);}_subscribe();}}@overrideWidget build(BuildContext context) => widget.builder(context, _snapshot);@overridevoid dispose() {_unsubscribe();super.dispose();}void _subscribe() {if (widget.future != null) {final Object callbackIdentity = Object();_activeCallbackIdentity = callbackIdentity;widget.future!.then<void>((T data) {if (_activeCallbackIdentity == callbackIdentity) {setState(() {_snapshot = AsyncSnapshot<T>.withData(ConnectionState.done, data);});}}, onError: (Object error, StackTrace stackTrace) {if (_activeCallbackIdentity == callbackIdentity) {setState(() {_snapshot = AsyncSnapshot<T>.withError(ConnectionState.done, error, stackTrace);});}assert(() {if(FutureBuilder.debugRethrowError) {Future<Object>.error(error, stackTrace);}return true;}());});_snapshot = _snapshot.inState(ConnectionState.waiting);}}void _unsubscribe() {_activeCallbackIdentity = null;}}
