You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中StatefulWidget类本地方法未执行问题解决

如何让Flutter页面加载时自动执行Firestore数据获取方法?

我开发了一个Flutter类用于展示Firestore数据库中的文档列表,已经编写了_getTrxnData方法来获取文档,但这个方法始终没执行。想知道怎么让它在页面加载时自动执行,同时我不太清楚这类操作的执行流程。相关代码如下:

class CompanyDashboardScreen extends ConsumerStatefulWidget {
  static const String id = 'user_dashboard_screen';

  const CompanyDashboardScreen({super.key});

  @override
  ConsumerState<CompanyDashboardScreen> createState() =
      _CompanyDashboardScreenState();
}

class _CompanyDashboardScreenState
    extends ConsumerState<CompanyDashboardScreen> {
  bool showSpinner = false;

  late List<Map<String, dynamic>> itemsTrxn;
  bool isLoaded = false;

  _getTrxnData() async {
    var collectionTrxn = FirebaseFirestore.instance
        .collection('company')
        .doc(ref.read(globalsNotifierProvider).companyId)
        .collection('trxns');

    List<Map<String, dynamic>> tempList = [];
    var dataTrxn = await collectionTrxn.get();

    dataTrxn.docs.forEach((element) {
      tempList.add(element.data());
    });

    setState(() {
      itemsTrxn = tempList;
      isLoaded = true;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: CustomAppBar(),
        drawer: Drawer(
          child: ListView(
            children: <Widget>[
              ListTile(
                title: const Text("User Profile"),
                onTap: () {
                  MainScreen.of(context)
                      ?.setIndex(3); // Added this for BottomNavigationBar sync
                  Navigator.of(context).push(MaterialPageRoute(
                      builder: (context) => const UserProfileScreen()));
                },
              ),
            ],
          ),
        ),
        body: SafeArea(
            child: isLoaded
                ? ListView.builder(
                    itemCount: itemsTrxn.length,
                    itemBuilder: (context, index) {
                      _getTrxnData(); <<<< METHOD HERE

                      return Padding(
                        padding: const EdgeInsets.all(8.0),
                        child: ListTile(
                          isThreeLine: true,
                          title: Row(
                            children: [
                              Text(
                                'Client: ${itemsTrxn[index]['clientFName'] ?? 'n/a'} ${itemsTrxn[index]['clientLName'] ?? 'n/a'}',
                                style: const TextStyle(
                                    fontWeight: FontWeight.w900,
                                    color: Colors.blueAccent),
                              ),
                            ],
                          ),
                          subtitle: Text.rich(
                            TextSpan(
                              text:
                                  '${itemsTrxn[index]['propertyAddress'] ?? 'n/a'}, '
                                  '${itemsTrxn[index]['propertyCity'] ?? 'n/a'}, '
                                  '${itemsTrxn[index]['propertyState'] ?? 'n/a'}',
                              children: <TextSpan>[
                                TextSpan(
                                  text:
                                      '\nPrice: ${itemsTrxn[index]['contractPrice'] == null ? 'n/a' : NumberFormat.simpleCurrency().format(itemsTrxn[index]['contractPrice'])}\nStatus: ${itemsTrxn[index]['trxnStatus'] ?? 'n/a'}',
                                  style: const TextStyle(
                                      fontWeight: FontWeight.w900,
                                      color: Colors.blueGrey),
                                )
                              ],
                            ),
                          ),
                          trailing: Text(
                              'MLS#: ${itemsTrxn[index]['mlsNumber'] ?? 'n/a'}\n${itemsTrxn[index]['clientType']}'),
                          onTap: () {
                            ref
                                .read(globalsNotifierProvider.notifier)
                                .updatenewTrxn(false);
                            ref
                                .read(globalsNotifierProvider.notifier)
                                .updatecurrentTrxnId(
                                    itemsTrxn[index]['trxnId']);
                            Navigator.of(context).push(MaterialPageRoute(
                                builder: (context) => TransactionDetailScreen(
                                    itemsTrxn[index]['trxnId'])));
                          },
                        ),
                      );
                    },
                  )
                : const Text('No Date')),
      ),
    );
  }
}

问题原因

你把_getTrxnData()放在了ListView.builder的itemBuilder里,但初始状态下isLoaded是false,页面会先渲染Text('No Date'),根本不会走到ListView.builder的逻辑里,所以方法完全没机会执行。就算后续isLoaded变成true,每次列表项构建时都会重复调用这个方法,导致多次请求Firestore,这也是不合理的。

解决方案:利用Flutter生命周期方法initState

Flutter的StatefulWidget有一套生命周期流程,其中initState方法会在Widget创建完成、第一次调用build之前执行,非常适合用来做初始化数据加载的操作。

修改步骤:

  1. 重写initState方法,在里面调用_getTrxnData
  2. 删除itemBuilder里的_getTrxnData()调用,避免重复请求

修改后的代码关键部分:

class _CompanyDashboardScreenState
    extends ConsumerState<CompanyDashboardScreen> {
  bool showSpinner = false;

  late List<Map<String, dynamic>> itemsTrxn;
  bool isLoaded = false;

  // 重写initState方法
  @override
  void initState() {
    super.initState();
    // 在页面初始化时调用数据获取方法
    _getTrxnData();
  }

  _getTrxnData() async {
    var collectionTrxn = FirebaseFirestore.instance
        .collection('company')
        .doc(ref.read(globalsNotifierProvider).companyId)
        .collection('trxns');

    List<Map<String, dynamic>> tempList = [];
    var dataTrxn = await collectionTrxn.get();

    dataTrxn.docs.forEach((element) {
      tempList.add(element.data());
    });

    setState(() {
      itemsTrxn = tempList;
      isLoaded = true;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        // ... 其余代码不变
        body: SafeArea(
            child: isLoaded
                ? ListView.builder(
                    itemCount: itemsTrxn.length,
                    itemBuilder: (context, index) {
                      // 删掉这里的_getTrxnData()调用
                      return Padding(
                        // ... 列表项代码不变
                      );
                    },
                  )
                : const Text('No Date')),
      ),
    );
  }
}

执行流程说明

  1. 页面创建,_CompanyDashboardScreenState初始化
  2. initState方法执行,调用_getTrxnData开始从Firestore获取数据
  3. 数据获取完成后,调用setState更新isLoaded为true,并赋值itemsTrxn
  4. setState触发build方法重新执行,此时isLoaded为true,渲染ListView.builder展示数据

内容的提问来源于stack exchange,提问作者LostTexan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 06:16:07