很多时候我们会依赖一些异步数据来动态更新UI:

  • 比如在打开一个页面时我们需要先从互联网上获取数据,在获取数据的过程中我们显示一个加载框,等获取到数据时我们再渲染页面;
  • 又比如我们想展示Stream(比如文件流、互联网数据接收流)的进度。

当然,通过StatefulWidget我们完全可以实现上述这些功能。但由于在实际开发中依赖异步数据更新UI的这种场景非常常见,因此Flutter专门提供了

  • FutureBuilder
  • StreamBuilder

两个组件来快速实现这种功能。

FutureBuilder

FutureBuilder 会依赖一个Future,它会根据所依赖的Future的状态来动态构建自身
我们看一下 FutureBuilder 构造函数:

  1. FutureBuilder({
  2. this.future,
  3. this.initialData,
  4. @required this.builder,
  5. })
  • future:FutureBuilder依赖的Future,通常是一个异步耗时任务
  • initialData:初始数据,用户设置默认数据
  • builder:Widget构建器;该构建器会在Future执行的不同阶段被多次调用,构建器签名如下:

    1. Function (BuildContext context, AsyncSnapshot snapshot)
    • snapshot 会包含当前异步任务的状态信息及结果信息 ,比如我们可以
      • 通过snapshot.connectionState 获取异步任务的状态信息
      • 通过snapshot.hasError 判断异步任务是否有错误等等,完整的定义读者可以查看AsyncSnapshot类定义

⚠️ Tip:另外,FutureBuilder的builder函数签名和StreamBuilder的builder是相同的
我们实现一个路由,当该路由打开时我们从网上获取数据,获取数据时弹一个加载框;获取结束时,如果成功则显示获取到的数据,如果失败则显示错误。由于我们还没有介绍在flutter中如何发起网络请求,所以在这里我们不真正去网络请求数据,而是模拟一下这个过程,隔3秒后返回一个字符串:

  1. Future<String> mockNetworkData() async {
  2. return Future.delayed(Duration(seconds: 2), () => "我是从互联网上获取的数据");
  3. }

FutureBuilder使用代码如下:

  1. import 'package:flutter/material.dart';
  2. void main() => runApp(new MyApp());
  3. class MyApp extends StatelessWidget {
  4. @override
  5. Widget build(BuildContext context) {
  6. return MaterialApp(
  7. debugShowCheckedModeBanner: true,
  8. title: '主题测试',
  9. theme: ThemeData(
  10. primarySwatch: Colors.blue,
  11. ),
  12. home: Scaffold(
  13. appBar: AppBar(title: Text("主题测试")),
  14. body: TestFutureBuilder(),
  15. ));
  16. }
  17. }
  18. Future<String> mockNetworkData() async {
  19. return Future.delayed(Duration(seconds: 2), () => "我是从互联网上获取的数据");
  20. }
  21. class TestFutureBuilder extends StatefulWidget {
  22. TestFutureBuilder({Key key}) : super(key: key);
  23. @override
  24. Test_FutureBuilderState createState() => Test_FutureBuilderState();
  25. }
  26. class Test_FutureBuilderState extends State<TestFutureBuilder> {
  27. @override
  28. Widget build(BuildContext context) {
  29. return Center(
  30. child: FutureBuilder<String>(
  31. future: mockNetworkData(),
  32. builder: (BuildContext context, AsyncSnapshot snapshot) {
  33. // 请求已结束
  34. if (snapshot.connectionState == ConnectionState.done) {
  35. if (snapshot.hasError) {
  36. // 请求失败,显示错误
  37. return Text("Error: ${snapshot.error}");
  38. } else {
  39. // 请求成功,显示数据
  40. return Text("Contents: ${snapshot.data}");
  41. }
  42. } else {
  43. // 请求未结束,显示loading
  44. return CircularProgressIndicator();
  45. }
  46. },
  47. ),
  48. );
  49. }
  50. }

运行结果如图所示:
异步UI更新(FutureBuilder、StreamBuilder) - 图1异步UI更新(FutureBuilder、StreamBuilder) - 图2
上面代码中我们在builder中根据当前异步任务状态ConnectionState来返回不同的widget。ConnectionState是一个枚举类,定义如下:

  1. enum ConnectionState {
  2. none, /// 当前没有异步任务,比如[FutureBuilder]的[future]为null时
  3. waiting, /// 异步任务处于等待状态
  4. active, /// Stream处于激活状态(流上已经有数据传递了),对于FutureBuilder没有该状态
  5. done, /// 异步任务已经终止
  6. }

注意,ConnectionState.active只在StreamBuilder中才会出现

StreamBuilder

我们知道,在Dart中 Stream 也是用于接收异步事件数据,和Future 不同的是,
它可以接收多个异步操作的结果,它常用于会多次读取数据的异步任务场景,如网络内容下载、文件读写等。StreamBuilder正是用于配合Stream来展示流上事件(数据)变化的UI组件。
下面看一下StreamBuilder的默认构造函数:

  1. StreamBuilder({
  2. Key key,
  3. this.initialData,
  4. Stream<T> stream,
  5. @required this.builder,
  6. })

可以看到和FutureBuilder的构造函数只有一点不同:前者需要一个future,而后者需要一个stream
我们创建一个计时器的示例:每隔1秒,计数加1。这里,我们使用Stream来实现每隔一秒生成一个数字:

  1. Stream<int> counter() {
  2. //创建以[周期]间隔重复发出事件的流。
  3. return Stream.periodic(Duration(milliseconds: 1), (i) {
  4. return i;
  5. });
  6. }

StreamBuilder使用代码如下:

  1. import 'package:flutter/material.dart';
  2. void main() => runApp(new MyApp());
  3. class MyApp extends StatelessWidget {
  4. @override
  5. Widget build(BuildContext context) {
  6. return MaterialApp(
  7. debugShowCheckedModeBanner: true,
  8. title: 'StreamBuilder',
  9. theme: ThemeData(
  10. primarySwatch: Colors.blue,
  11. ),
  12. home: Scaffold(
  13. appBar: AppBar(title: Text("StreamBuilder")),
  14. body: TestFutureBuilder(),
  15. ));
  16. }
  17. }
  18. Stream<int> counter() {
  19. //创建以[周期]间隔重复发出事件的流。
  20. return Stream.periodic(Duration(milliseconds: 1), (i) {
  21. return i;
  22. });
  23. }
  24. class TestFutureBuilder extends StatefulWidget {
  25. TestFutureBuilder({Key key}) : super(key: key);
  26. @override
  27. Test_FutureBuilderState createState() => Test_FutureBuilderState();
  28. }
  29. class Test_FutureBuilderState extends State<TestFutureBuilder> {
  30. @override
  31. Widget build(BuildContext context) {
  32. return StreamBuilder<int>(
  33. stream: counter(), //
  34. //initialData: ,// a Stream<int> or null
  35. builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
  36. if (snapshot.hasError)
  37. return Text('Error: ${snapshot.error}');
  38. switch (snapshot.connectionState) {
  39. case ConnectionState.none:
  40. return Text('没有Stream');
  41. case ConnectionState.waiting:
  42. return Text('等待数据...');
  43. case ConnectionState.active:
  44. return Text('active: ${snapshot.data}');
  45. case ConnectionState.done:
  46. return Text('Stream已关闭');
  47. }
  48. return null; // unreachable
  49. },
  50. );
  51. }
  52. }

读者可以自己运行本示例查看运行结果。注意,本示例只是为了演示StreamBuilder的使用,在实战中,凡是UI会依赖多个异步数据而发生变化的场景都可以使用StreamBuilder

FutureBuilder和StreamBuilder源码

  1. import 'dart:async' show StreamSubscription;
  2. import 'package:flutter/foundation.dart';
  3. import 'framework.dart';
  4. abstract class StreamBuilderBase<T, S> extends StatefulWidget {
  5. const StreamBuilderBase({ Key? key, this.stream }) : super(key: key);
  6. final Stream<T>? stream;
  7. S initial();
  8. S afterConnected(S current) => current;
  9. S afterData(S current, T data);
  10. S afterError(S current, Object error, StackTrace stackTrace) => current;
  11. S afterDone(S current) => current;
  12. S afterDisconnected(S current) => current;
  13. Widget build(BuildContext context, S currentSummary);
  14. @override
  15. State<StreamBuilderBase<T, S>> createState() => _StreamBuilderBaseState<T, S>();
  16. }
  17. class _StreamBuilderBaseState<T, S> extends State<StreamBuilderBase<T, S>> {
  18. StreamSubscription<T>? _subscription; // ignore: cancel_subscriptions
  19. late S _summary;
  20. @override
  21. void initState() {
  22. super.initState();
  23. _summary = widget.initial();
  24. _subscribe();
  25. }
  26. @override
  27. void didUpdateWidget(StreamBuilderBase<T, S> oldWidget) {
  28. super.didUpdateWidget(oldWidget);
  29. if (oldWidget.stream != widget.stream) {
  30. if (_subscription != null) {
  31. _unsubscribe();
  32. _summary = widget.afterDisconnected(_summary);
  33. }
  34. _subscribe();
  35. }
  36. }
  37. @override
  38. Widget build(BuildContext context) => widget.build(context, _summary);
  39. @override
  40. void dispose() {
  41. _unsubscribe();
  42. super.dispose();
  43. }
  44. void _subscribe() {
  45. if (widget.stream != null) {
  46. _subscription = widget.stream!.listen((T data) {
  47. setState(() {
  48. _summary = widget.afterData(_summary, data);
  49. });
  50. }, onError: (Object error, StackTrace stackTrace) {
  51. setState(() {
  52. _summary = widget.afterError(_summary, error, stackTrace);
  53. });
  54. }, onDone: () {
  55. setState(() {
  56. _summary = widget.afterDone(_summary);
  57. });
  58. });
  59. _summary = widget.afterConnected(_summary);
  60. }
  61. }
  62. void _unsubscribe() {
  63. if (_subscription != null) {
  64. _subscription!.cancel();
  65. _subscription = null;
  66. }
  67. }
  68. }
  69. enum ConnectionState {
  70. none,
  71. waiting,
  72. active,
  73. done,
  74. }
  75. @immutable
  76. class AsyncSnapshot<T> {
  77. const AsyncSnapshot._(this.connectionState, this.data, this.error, this.stackTrace)
  78. : assert(connectionState != null),
  79. assert(!(data != null && error != null)),
  80. assert(stackTrace == null || error != null);
  81. const AsyncSnapshot.nothing() : this._(ConnectionState.none, null, null, null);
  82. const AsyncSnapshot.waiting() : this._(ConnectionState.waiting, null, null, null);
  83. const AsyncSnapshot.withData(ConnectionState state, T data): this._(state, data, null, null);
  84. const AsyncSnapshot.withError(
  85. ConnectionState state,
  86. Object error, [
  87. StackTrace stackTrace = StackTrace.empty,
  88. ]) : this._(state, null, error, stackTrace);
  89. final ConnectionState connectionState;
  90. final T? data;
  91. T get requireData {
  92. if (hasData)
  93. return data!;
  94. if (hasError)
  95. throw error!;
  96. throw StateError('Snapshot has neither data nor error');
  97. }
  98. final Object? error;
  99. final StackTrace? stackTrace;
  100. AsyncSnapshot<T> inState(ConnectionState state) => AsyncSnapshot<T>._(state, data, error, stackTrace);
  101. bool get hasData => data != null;
  102. bool get hasError => error != null;
  103. @override
  104. String toString() => '${objectRuntimeType(this, 'AsyncSnapshot')}($connectionState, $data, $error, $stackTrace)';
  105. @override
  106. bool operator ==(Object other) {
  107. if (identical(this, other))
  108. return true;
  109. return other is AsyncSnapshot<T>
  110. && other.connectionState == connectionState
  111. && other.data == data
  112. && other.error == error
  113. && other.stackTrace == stackTrace;
  114. }
  115. @override
  116. int get hashCode => hashValues(connectionState, data, error);
  117. }
  118. typedef AsyncWidgetBuilder<T> = Widget Function(BuildContext context, AsyncSnapshot<T> snapshot);
  119. class StreamBuilder<T> extends StreamBuilderBase<T, AsyncSnapshot<T>> {
  120. const StreamBuilder({
  121. Key? key,
  122. this.initialData,
  123. Stream<T>? stream,
  124. required this.builder,
  125. }) : assert(builder != null),
  126. super(key: key, stream: stream);
  127. final AsyncWidgetBuilder<T> builder;
  128. final T? initialData;
  129. @override
  130. AsyncSnapshot<T> initial() => initialData == null
  131. ? AsyncSnapshot<T>.nothing()
  132. : AsyncSnapshot<T>.withData(ConnectionState.none, initialData as T);
  133. @override
  134. AsyncSnapshot<T> afterConnected(AsyncSnapshot<T> current) => current.inState(ConnectionState.waiting);
  135. @override
  136. AsyncSnapshot<T> afterData(AsyncSnapshot<T> current, T data) {
  137. return AsyncSnapshot<T>.withData(ConnectionState.active, data);
  138. }
  139. @override
  140. AsyncSnapshot<T> afterError(AsyncSnapshot<T> current, Object error, StackTrace stackTrace) {
  141. return AsyncSnapshot<T>.withError(ConnectionState.active, error, stackTrace);
  142. }
  143. @override
  144. AsyncSnapshot<T> afterDone(AsyncSnapshot<T> current) => current.inState(ConnectionState.done);
  145. @override
  146. AsyncSnapshot<T> afterDisconnected(AsyncSnapshot<T> current) => current.inState(ConnectionState.none);
  147. @override
  148. Widget build(BuildContext context, AsyncSnapshot<T> currentSummary) => builder(context, currentSummary);
  149. }
  150. // TODO(ianh): remove unreachable code above once https://github.com/dart-lang/sdk/issues/35520 is fixed
  151. class FutureBuilder<T> extends StatefulWidget {
  152. const FutureBuilder({
  153. Key? key,
  154. this.future,
  155. this.initialData,
  156. required this.builder,
  157. }) : assert(builder != null),
  158. super(key: key);
  159. final Future<T>? future;
  160. final AsyncWidgetBuilder<T> builder;
  161. final T? initialData;
  162. static bool debugRethrowError = false;
  163. @override
  164. State<FutureBuilder<T>> createState() => _FutureBuilderState<T>();
  165. }
  166. class _FutureBuilderState<T> extends State<FutureBuilder<T>> {
  167. Object? _activeCallbackIdentity;
  168. late AsyncSnapshot<T> _snapshot;
  169. @override
  170. void initState() {
  171. super.initState();
  172. _snapshot = widget.initialData == null
  173. ? AsyncSnapshot<T>.nothing()
  174. : AsyncSnapshot<T>.withData(ConnectionState.none, widget.initialData as T);
  175. _subscribe();
  176. }
  177. @override
  178. void didUpdateWidget(FutureBuilder<T> oldWidget) {
  179. super.didUpdateWidget(oldWidget);
  180. if (oldWidget.future != widget.future) {
  181. if (_activeCallbackIdentity != null) {
  182. _unsubscribe();
  183. _snapshot = _snapshot.inState(ConnectionState.none);
  184. }
  185. _subscribe();
  186. }
  187. }
  188. @override
  189. Widget build(BuildContext context) => widget.builder(context, _snapshot);
  190. @override
  191. void dispose() {
  192. _unsubscribe();
  193. super.dispose();
  194. }
  195. void _subscribe() {
  196. if (widget.future != null) {
  197. final Object callbackIdentity = Object();
  198. _activeCallbackIdentity = callbackIdentity;
  199. widget.future!.then<void>((T data) {
  200. if (_activeCallbackIdentity == callbackIdentity) {
  201. setState(() {
  202. _snapshot = AsyncSnapshot<T>.withData(ConnectionState.done, data);
  203. });
  204. }
  205. }, onError: (Object error, StackTrace stackTrace) {
  206. if (_activeCallbackIdentity == callbackIdentity) {
  207. setState(() {
  208. _snapshot = AsyncSnapshot<T>.withError(ConnectionState.done, error, stackTrace);
  209. });
  210. }
  211. assert(() {
  212. if(FutureBuilder.debugRethrowError) {
  213. Future<Object>.error(error, stackTrace);
  214. }
  215. return true;
  216. }());
  217. });
  218. _snapshot = _snapshot.inState(ConnectionState.waiting);
  219. }
  220. }
  221. void _unsubscribe() {
  222. _activeCallbackIdentity = null;
  223. }
  224. }