如果需要 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';
@override
State<OrdersScreen> createState() => _OrdersScreenState();
}
class _OrdersScreenState extends State<OrdersScreen> {
var _isLoading = false;
@override
void initState() {
Future.delayed(Duration.zero).then((_) async {
setState(() {
_isLoading = true;
});
await Provider.of<Orders>(context, listen: false).fetchAndSetOrders();
setState(() {
_isLoading = false;
});
});
super.initState();
}
@override
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';
@override
State<OrdersScreen> createState() => _OrdersScreenState();
}
class _OrdersScreenState extends State<OrdersScreen> {
Future _ordersFuture;
Future _obtainOrdersFuture() {
return Provider.of<Orders>(context, listen: false).fetchAndSetOrders();
}
@override
void initState() {
_ordersFuture = _obtainOrdersFuture();
super.initState();
}
@override
Widget build(BuildContext context) {
print('building orders');
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,直到数据到来。
Tasks for ListBuilder :
ListBuilder 的任务:
- Have two state variables, dataFromBackend and isLoadingFlag
- On launch, set isLoadingFlag = true, and based on this, show loader.
- Once data arrives, set data with what you get from backend and set isLoadingFlag = false (inside setState obviously)
- We need to have a if-else in widget creation. If isLoadingFlag is true, show the loader else show the data. On failure, show error message.
- 有两个状态变量,
dataFromBackend 和 isLoadingFlag - 在启动时,设置
isLoadingFlag = true ,并在此基础上显示 loader。 - 数据到达后,使用从后端获得的数据设置数据并设置
isLoadingFlag = false (显然在 setState 内部) - 我们需要在创建小部件时使用
if-else 。如果 isLoadingFlag 为 true ,则显示 loader,否则显示数据。失败时,显示错误消息。
Tasks for FutureBuilder :
FutureBuilder 的任务:
- Give the async task in
future of Future Builder - Based on connectionState, show message (
loading , active(streams) , done ) - Based on
data(snapshot.hasError) , show view
- 给 Future Builder 未来的异步任务 ??
- 基于
connectionState ,显示消息 (loading , active(streams) , done ) - 基于数据(
snapshot.hasError ),显示视图
Pros of FutureBuilder
FutureBuilder 的优点
- Does not use the two state variables and
setState - Reactive programming (FutureBuilder will take care of updating the view on data arrival)
Example:
- 不使用两个状态变量和
setState - 反应式编程(
FutureBuilder 将负责在数据到达时更新视图) 例子:
FutureBuilder<String>(
future: _fetchNetworkCall,
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}');
}
},
)
Performance impact:
性能影响:
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<T>.withData(ConnectionState.none, widget.initialData); 它订阅了 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 是我们通常做的一个包装器/样板,因此不应该有任何性能影响。
|