很多时候我们会依赖一些异步数据来动态更新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 {
@override
Widget 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);
@override
Test_FutureBuilderState createState() => Test_FutureBuilderState();
}
class Test_FutureBuilderState extends State<TestFutureBuilder> {
@override
Widget 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 {
// 请求未结束,显示loading
return 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 {
@override
Widget 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);
@override
Test_FutureBuilderState createState() => Test_FutureBuilderState();
}
class Test_FutureBuilderState extends State<TestFutureBuilder> {
@override
Widget build(BuildContext context) {
return StreamBuilder<int>(
stream: counter(), //
//initialData: ,// a Stream<int> or null
builder: (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);
@override
State<StreamBuilderBase<T, S>> createState() => _StreamBuilderBaseState<T, S>();
}
class _StreamBuilderBaseState<T, S> extends State<StreamBuilderBase<T, S>> {
StreamSubscription<T>? _subscription; // ignore: cancel_subscriptions
late S _summary;
@override
void initState() {
super.initState();
_summary = widget.initial();
_subscribe();
}
@override
void didUpdateWidget(StreamBuilderBase<T, S> oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.stream != widget.stream) {
if (_subscription != null) {
_unsubscribe();
_summary = widget.afterDisconnected(_summary);
}
_subscribe();
}
}
@override
Widget build(BuildContext context) => widget.build(context, _summary);
@override
void 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,
}
@immutable
class 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;
@override
String toString() => '${objectRuntimeType(this, 'AsyncSnapshot')}($connectionState, $data, $error, $stackTrace)';
@override
bool 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;
}
@override
int 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;
@override
AsyncSnapshot<T> initial() => initialData == null
? AsyncSnapshot<T>.nothing()
: AsyncSnapshot<T>.withData(ConnectionState.none, initialData as T);
@override
AsyncSnapshot<T> afterConnected(AsyncSnapshot<T> current) => current.inState(ConnectionState.waiting);
@override
AsyncSnapshot<T> afterData(AsyncSnapshot<T> current, T data) {
return AsyncSnapshot<T>.withData(ConnectionState.active, data);
}
@override
AsyncSnapshot<T> afterError(AsyncSnapshot<T> current, Object error, StackTrace stackTrace) {
return AsyncSnapshot<T>.withError(ConnectionState.active, error, stackTrace);
}
@override
AsyncSnapshot<T> afterDone(AsyncSnapshot<T> current) => current.inState(ConnectionState.done);
@override
AsyncSnapshot<T> afterDisconnected(AsyncSnapshot<T> current) => current.inState(ConnectionState.none);
@override
Widget 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 fixed
class 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;
@override
State<FutureBuilder<T>> createState() => _FutureBuilderState<T>();
}
class _FutureBuilderState<T> extends State<FutureBuilder<T>> {
Object? _activeCallbackIdentity;
late AsyncSnapshot<T> _snapshot;
@override
void initState() {
super.initState();
_snapshot = widget.initialData == null
? AsyncSnapshot<T>.nothing()
: AsyncSnapshot<T>.withData(ConnectionState.none, widget.initialData as T);
_subscribe();
}
@override
void didUpdateWidget(FutureBuilder<T> oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.future != widget.future) {
if (_activeCallbackIdentity != null) {
_unsubscribe();
_snapshot = _snapshot.inState(ConnectionState.none);
}
_subscribe();
}
}
@override
Widget build(BuildContext context) => widget.builder(context, _snapshot);
@override
void 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;
}
}