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

Flutter应用:如何避免Firestore重复加载及FutureBuilder等待?

Flutter侧边抽屉优化:移除FutureBuilder并解决数据加载问题

问题背景

我在Flutter应用中实现了自定义侧边抽屉(SideDrawer),点击按钮可打开;抽屉内的InfoCard组件用于展示用户信息。当前该组件通过FutureBuilder实现,每次打开抽屉都会显示CircularProgressIndicator,直至从Firestore服务器获取数据。

需求与困惑

  • 需求:移除FutureBuilder,在应用完全加载时就获取InfoCard所需的全部数据。
  • 困惑1:明明应用启动时已加载Firestore数据,为何打开抽屉仍需等待数据库加载才能显示InfoCard?
  • 困惑2:若直接使用InfoCard而非FutureBuilder,会在userP.measurements.firstWhere((measure) => measure.title == 'Weight')代码行抛出StateError (Bad state: No element)错误。
  • 困惑3:为何已在应用启动时加载Firestore数据,infocard方法仍需再次调用数据库?

相关代码

侧边抽屉组件代码

class SideDrawer extends ConsumerStatefulWidget {
  SideDrawer({
    super.key,
    required this.setIndex,
  });

  Function(int index) setIndex;

  @override
  ConsumerState<SideDrawer> createState() => _SideDrawerState();
}

class _SideDrawerState extends ConsumerState<SideDrawer> {
  @override
  Widget build(BuildContext context) {
    final userP = ref.watch(userStateNotifier);

    return Scaffold(
      body: Container(
        width: MediaQuery.sizeOf(context).width * 3 / 4,
        height: double.infinity,
        color: Theme.of(context).colorScheme.secondary,
        child: SafeArea(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              FutureBuilder<Widget>(
                future: Datas().infocard(userP),
                builder: (context, snapshot) {
                  if (snapshot.connectionState == ConnectionState.done) {
                    if (snapshot.hasError) {
                      return Text('Errore: ${snapshot.error}');
                    }
                    return snapshot.data!;
                  } else {
                    return CircularProgressIndicator();
                  }
                },
              ),
              ColDrawer(setIndex: (index) {
                widget.setIndex(index);
              },),
            ],
          ),
        ),
      ),
    );
  }
}

infocard方法代码

Future<Widget> infocard(AppUser userP) async {
    var db = FirebaseFirestore.instance;
    QuerySnapshot qn =
        await db.collection('users').where('id', isEqualTo: userP.id).get();
    return InfoCard(
      username: userP.username,
      age: userP.age,
      weight: userP.measurements
          .firstWhere((measure) => measure.title == 'Weight')
          .datas
          .last
          .values
          .first,
    );
  }

问题分析与解决方案

1. 重复Firestore请求的原因

你的infocard方法里发起了Firestore查询,但构造InfoCard时完全用的是传入的userP对象字段,这个查询完全多余——既没用到查询结果,还导致每次打开抽屉都要等待网络请求,这就是为什么明明预加载了数据还要等加载的原因。

2. 直接使用InfoCard报错的原因

firstWhere方法在找不到匹配元素时会抛出StateError,说明:

  • 应用启动时预加载的用户数据不完整,userP.measurements里没有title为Weight的项;
  • 或者用户数据还没完成初始化,就提前渲染了InfoCard。

3. 优化步骤

步骤1:移除多余的Firestore请求,处理空数据

修改infocard方法,删掉无用的查询,给firstWhere添加默认值避免报错,同时处理datas为空的情况:

Widget infocard(AppUser userP) {
  // 找不到Weight项时返回默认空对象
  final weightMeasure = userP.measurements.firstWhere(
    (measure) => measure.title == 'Weight',
    orElse: () => Measurement(title: 'Weight', datas: []),
  );
  // 处理datas为空的情况,避免调用last报错
  final weightValue = weightMeasure.datas.isNotEmpty 
      ? weightMeasure.datas.last.values.first 
      : 'N/A';

  return InfoCard(
    username: userP.username,
    age: userP.age,
    weight: weightValue,
  );
}

步骤2:确保应用启动时加载完整用户数据

在Riverpod的UserStateNotifier中,初始化时加载包含完整measurements的用户数据,确保状态中的AppUser对象数据完整:

class UserStateNotifier extends StateNotifier<AppUser> {
  UserStateNotifier() : super(AppUser.empty()) {
    _loadFullUserData();
  }

  Future<void> _loadFullUserData() async {
    final currentUser = FirebaseAuth.instance.currentUser;
    if (currentUser == null) return;

    final userDoc = await FirebaseFirestore.instance
        .collection('users')
        .doc(currentUser.uid)
        .get();

    if (userDoc.exists) {
      // 解析Firestore数据为包含完整measurements的AppUser对象
      state = AppUser.fromFirestore(userDoc.data()!);
    }
  }
}

步骤3:修改SideDrawer,直接使用预加载数据

移除FutureBuilder,直接调用修改后的infocard,同时添加数据加载状态判断:

class _SideDrawerState extends ConsumerState<SideDrawer> {
  @override
  Widget build(BuildContext context) {
    final userP = ref.watch(userStateNotifier);

    // 判断用户数据是否加载完成(根据你的AppUser模型调整判断条件)
    if (userP.id.isEmpty || userP.measurements.isEmpty) {
      return const Center(child: CircularProgressIndicator());
    }

    return Scaffold(
      body: Container(
        width: MediaQuery.sizeOf(context).width * 3 / 4,
        height: double.infinity,
        color: Theme.of(context).colorScheme.secondary,
        child: SafeArea(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              // 直接使用预加载的数据渲染InfoCard
              Datas().infocard(userP),
              ColDrawer(setIndex: (index) {
                widget.setIndex(index);
              },),
            ],
          ),
        ),
      ),
    );
  }
}

总结

  • 重复请求Firestore是因为infocard里的查询完全多余,删掉即可解决每次打开抽屉等待加载的问题;
  • firstWhere报错是因为数据不完整或未处理空值,添加orElse和空判断就能避免;
  • 应用启动时要确保加载完整的用户数据到状态中,这样打开抽屉时就能直接使用,无需再发起网络请求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 16:17:21