Flutter中FutureBuilder展示订单触发无限循环问题排查
Flutter FutureBuilder无限循环问题修复与替代方案
无限循环原因分析
- FutureBuilder的future参数重复执行:在
build方法中直接调用Provider.of<Orders>(context, listen: false).fetchandsetorders()作为FutureBuilder的future参数,每次页面重建时都会创建新的Future实例,触发新一轮数据请求。而请求完成后调用notifyListeners()会再次触发页面重建,形成循环。 - 不必要的页面重建触发:AppBar的
backgroundColor使用Random()生成随机值,每次build都会生成新颜色,强制页面重建,进一步加剧循环。
代码修正步骤
1. 初始化Future到initState,避免重复执行
将数据请求的Future移到initState中初始化,确保只在页面初始化时执行一次:
class _OrderScreenState extends State<OrderScreen> { late Future<void> _fetchOrdersFuture; @override void initState() { super.initState(); // 仅在页面初始化时触发一次数据请求 _fetchOrdersFuture = Provider.of<Orders>(context, listen: false).fetchandsetorders(); } @override Widget build(BuildContext context) { final orderdata = Provider.of<Orders>(context); print('I am buildmethod'); return Scaffold( appBar: AppBar( backgroundColor: Colors.blue, // 替换随机颜色,避免不必要的页面重建 title: Text('List of orders: ' + orderdata.ordercount.toString()), ), drawer: AppDrawer(), body: FutureBuilder( future: _fetchOrdersFuture, // 使用initState中初始化的Future实例 builder: (context, snapshot){ if(snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } else { if(snapshot.error != null) { return Text('加载失败: ${snapshot.error}'); } else { return ListView.builder( itemCount: orderdata.ordercount, itemBuilder: (context, index) { return OrderItemWidget( order: orderdata.orders[index], ); }); } } }, ), ); } }
2. 完善Provider的异常处理
在fetchandsetorders方法中添加异常捕获,避免请求失败时无提示:
Future<void> fetchandsetorders() async { print('I am fetchandsetorders method of provider'); try { final url = Uri.parse(mylink); final response = await http.get(url); // 检查响应状态码,确保请求成功 if (response.statusCode != 200) { throw Exception('请求失败,状态码: ${response.statusCode}'); } final List<OrderItem> loadedorders = []; final Map<String, dynamic> extradeddata = json.decode(response.body) as Map<String, dynamic>; extradeddata.forEach((orderid, orderdata) { loadedorders.add( OrderItem( id: orderid, products: (orderdata['products'] as List<dynamic>).map((item) { return CartItem( id: item['id'], title: item['title'], quantity: item['qty'], price: item['price']); }).toList(), amount: orderdata['amount'], date: DateTime.parse(orderdata['date']), ), ); }); _orders = loadedorders.reversed.toList(); notifyListeners(); } catch (e) { rethrow; // 将异常抛出,让FutureBuilder捕获并处理 } }
替代FutureBuilder的更优方案:结合Provider状态管理
直接在Provider中管理加载状态、错误状态和数据,通过Consumer监听状态变化,代码逻辑更清晰,避免FutureBuilder的常见陷阱:
1. 修改Provider,添加状态管理
class Orders with ChangeNotifier { List<OrderItem> _orders = []; bool _isLoading = false; String? _error; List<OrderItem> get orders => [..._orders]; int get ordercount => _orders.length; bool get isLoading => _isLoading; String? get error => _error; Future<void> fetchandsetorders() async { _isLoading = true; _error = null; notifyListeners(); // 通知页面显示加载状态 try { final url = Uri.parse(mylink); final response = await http.get(url); if (response.statusCode != 200) { throw Exception('请求失败'); } final Map<String, dynamic> extradeddata = json.decode(response.body) as Map<String, dynamic>; final List<OrderItem> loadedorders = []; extradeddata.forEach((orderid, orderdata) { loadedorders.add( OrderItem( id: orderid, products: (orderdata['products'] as List<dynamic>).map((item) => CartItem( id: item['id'], title: item['title'], quantity: item['qty'], price: item['price'] )).toList(), amount: orderdata['amount'], date: DateTime.parse(orderdata['date']), ), ); }); _orders = loadedorders.reversed.toList(); } catch (e) { _error = e.toString(); } finally { _isLoading = false; notifyListeners(); // 通知页面加载完成,更新状态 } } }
2. 修改页面代码,使用Consumer监听状态
class _OrderScreenState extends State<OrderScreen> { @override void initState() { super.initState(); // 页面初始化时触发数据加载 Provider.of<Orders>(context, listen: false).fetchandsetorders(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Colors.blue, title: Consumer<Orders>( builder: (context, orderdata, child) => Text('List of orders: ${orderdata.ordercount}'), ), ), drawer: AppDrawer(), body: Consumer<Orders>( builder: (context, orderdata, child) { // 根据状态渲染不同UI if (orderdata.isLoading) { return const Center(child: CircularProgressIndicator()); } if (orderdata.error != null) { return Text('加载失败: ${orderdata.error}'); } return ListView.builder( itemCount: orderdata.ordercount, itemBuilder: (context, index) => OrderItemWidget(order: orderdata.orders[index]), ); }, ), ); } }
内容的提问来源于stack exchange,提问作者Irfan Ganatra
相关产品推荐
相关产品推荐

