如果需要 render 的数据需要使用 http 请求从后端获取,那么从发送请求到最终获得数据之间必然会有延时,使用一般的方法,就需要加上额外的变量如 _isLoading 判断数据是否在加载,如果是,则加载 spinner 组件等等,代码相对繁琐:
以下是一个显示若干条订单 (orders) 的页面,orders_screen.dart:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../widgets/app_drawer.dart';
import '../providers/orders.dart' show Orders;
import '../widgets/order_item.dart';
class OrdersScreen extends StatefulWidget {
static const routeName = '/orders';
State<OrdersScreen> createState() => _OrdersScreenState();
}
class _OrdersScreenState extends State<OrdersScreen> {
var _isLoading = false;
void initState() {
Future.delayed(Duration.zero).then((_) async {
setState(() {
_isLoading = true;
});
await Provider.of<Orders>(context, listen: false).fetchAndSetOrders();
setState(() {
_isLoading = false;
});
});
super.initState();
}
Widget build(BuildContext context) {
final orderData = Provider.of<Orders>(context);
return Scaffold(
appBar: AppBar(
title: Text('Orders Screen'),
),
drawer: AppDrawer(),
body: _isLoading
? Center(child: CircularProgressIndicator())
: ListView.builder(
itemCount: orderData.orders.length,
itemBuilder: (ctx, i) => OrderItem(orderData.orders[i]),
),
);
}
}
改为使用 FutureBuilder,代码比较起来更为优雅:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../widgets/app_drawer.dart';
import '../providers/orders.dart' show Orders;
import '../widgets/order_item.dart';
class OrdersScreen extends StatefulWidget {
static const routeName = '/orders';
State<OrdersScreen> createState() => _OrdersScreenState();
}
class _OrdersScreenState extends State<OrdersScreen> {
Future _ordersFuture;
Future _obtainOrdersFuture() {
return Provider.of<Orders>(context, listen: false).fetchAndSetOrders();
}
void initState() {
_ordersFuture = _obtainOrdersFuture();
super.initState();
}
Widget build(BuildContext context) {
print('building orders');
// final orderData = Provider.of(context);
return Scaffold(
appBar: AppBar(
title: Text('Orders Screen'),
),
drawer: AppDrawer(),
body: FutureBuilder(
future: _ordersFuture,
builder: (ctx, dataSnapshot) {
if (dataSnapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else {
if (dataSnapshot.error != null) {
return Center(child: Text('An error occurred.'));
} else {
return Consumer<Orders>(
builder: (ctx, orderData, child) => ListView.builder(
itemCount: orderData.orders.length,
itemBuilder: (ctx, i) => OrderItem(orderData.orders[i]),
),
);
}
}
},
),
);
}
}
StackOverflow 上的 FutureBuilder :
https://stackoverflow.com/questions/51983011/when-should-i-use-a-futurebuilder
FutureBuilder removes boilerplate code.
Let’s say you want to fetch some data from the backend on page launch and show a loader until data comes.
FutureBuilder 删除了样板代码。
假设在页面启动时需要从后端获取一些数据,在此期间显示 loader,直到数据到来。
ListBuilder:dataFromBackend 和 isLoadingFlagisLoadingFlag = true,并在此基础上显示 loader。isLoadingFlag = false (显然在 setState 内部)if-else。如果 isLoadingFlag 为 true,则显示 loader,否则显示数据。失败时,显示错误消息。FutureBuilder:FutureBuilder 的任务:future of Future Builderloading, active(streams), done)data(snapshot.hasError), show viewconnectionState,显示消息 (loading, active(streams), done)snapshot.hasError),显示视图FutureBuilderFutureBuilder 的优点setStatesetStateFutureBuilder 将负责在数据到达时更新视图)FutureBuilder<String>(
future: _fetchNetworkCall, // async work
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting: return Text('Loading....');
default:
if (snapshot.hasError)
return Text('Error: ${snapshot.error}');
else
return Text('Result: ${snapshot.data}');
}
},
)
I just looked into the FutureBuilder code to understand the performance impact of using this.
FutureBuilder is just a StatefulWidget whose state variable is _snapshot
Initial state is _snapshot = AsyncSnapshot.withData(ConnectionState.none, widget.initialData);
It is subscribing to future which we send via the constructor and update the state based on that.
Example:
我只是查看了 FutureBuilder 代码以了解使用它对性能的影响。
FutureBuilder 只是一个 StatefulWidget,它的 state 变量是 _snapshot
初始状态为 _snapshot = AsyncSnapshot
它订阅了 future, 我们通过构造函数发送 future, 并基于它更新 state。
例子:
widget.future.then<void>((T data) {
if (_activeCallbackIdentity == callbackIdentity) {
setState(() {
_snapshot = AsyncSnapshot<T>.withData(ConnectionState.done, data);
});
}
}, onError: (Object error) {
if (_activeCallbackIdentity == callbackIdentity) {
setState(() {
_snapshot = AsyncSnapshot<T>.withError(ConnectionState.done, error);
});
}
});
So the FutureBuilder is a wrapper/boilerplate of what we do typically, hence there should not be any performance impact.
所以 FutureBuilder 是我们通常做的一个包装器/样板,因此不应该有任何性能影响。