• Flutter: FutureBuilder 组件的使用


    如果需要 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]),
                ),
        );
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    改为使用 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]),
                    ),
                  );
                }
              }
            },
          ),
        );
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    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.

    • 有两个状态变量,dataFromBackendisLoadingFlag
    • 在启动时,设置 isLoadingFlag = true,并在此基础上显示 loader。
    • 数据到达后,使用从后端获得的数据设置数据并设置 isLoadingFlag = false (显然在 setState 内部)
    • 我们需要在创建小部件时使用 if-else。如果 isLoadingFlagtrue,则显示 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, // 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}');
            }
          },
        )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    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.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);
        });
      }
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    So the FutureBuilder is a wrapper/boilerplate of what we do typically, hence there should not be any performance impact.

    所以 FutureBuilder 是我们通常做的一个包装器/样板,因此不应该有任何性能影响。

  • 相关阅读:
    2024最新版JavaScript逆向爬虫教程-------基础篇之深入JavaScript运行原理以及内存管理
    Linux系统的特点以及年轻人如何获取第一个Linux系统
    126. SAP UI5 进阶 - JSON 模型字段里的值,显示在最终 UI5 界面上的奥秘分析
    C语言网络编程基础(linux)
    C++ 之 宏定义
    TDengine高可用架构之TDengine+Keepalived
    【Linux】gdb调试器
    Self-Polish: Enhance Reasoning in Large Language Models via Problem Refinement
    babel转换class时使用defineProperty导致的装饰器问题
    「PHP基础知识」隐式数据类型
  • 原文地址:https://blog.csdn.net/ftell/article/details/126609404