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
相关产品推荐
相关产品推荐

