Riverpod 2在showModalBottomSheet中无响应,如何实现实时状态更新?
Riverpod 2配合showModalBottomSheet状态更新问题
问题描述
使用Riverpod 2管理Flutter外卖应用状态时,在showModalBottomSheet(简称sMBS)中遇到状态更新异常:
- 已通过
Consumer实现商品数量增减的实时UI更新 - 点击
FilterChip添加/移除附加选项时,价格不会实时变化,必须关闭并重新打开sMBS才能看到更新
核心问题分析
- 未监听附加选项列表状态:计算商品总价时没有监听
cartAddonListProvider的变化,导致附加选项变更时无法触发UI重绘 - 错误的列表更新方式:直接调用列表的
add/remove方法修改cartAddonListProvider的状态——Riverpod的StateProvider依赖对象引用变化检测更新,可变对象的内部修改无法被感知
修复方案与代码示例
修复要点
- 在顶层
Consumer中监听cartAddonListProvider,确保附加选项变化时触发UI重绘 - 通过创建新列表的方式更新
cartAddonListProvider状态,而非修改原列表 - 计算总价时加入附加选项的价格(示例中按每个附加选项固定价计算,可根据实际业务调整)
修复后代码
onTap: () { Log.error('Tapped'); showModalBottomSheet( isScrollControlled: true, useRootNavigator: true, backgroundColor: Colors.white, useSafeArea: true, context: context, builder: (context) { return Consumer( builder: (context, ref, child) { final cartItemQty = ref.watch(cartQtyProvider); final addonList = ref.watch(cartAddonListProvider); // 监听附加选项列表 Log.error('message====> ${addonList.length}'); // 计算包含附加选项的总价:商品单价*数量 + 附加选项总价(示例每个附加选项加100 FCFA) const int addonPrice = 100; int totalAddonCost = addonList.length * addonPrice; int productPrice = product[index].price * cartItemQty + totalAddonCost; return Wrap(children: [ Padding( padding: const EdgeInsets.all(8.0), child: Column( children: [ Image.network( 'http://localhost:8055/assets/${product[index].image}', width: 50, height: 50, ), Text(product[index].name, style: AppStyles.headLineText), const Gap(20), Row( children: [ AppIconBtn( onPressed: cartItemQty <= 1 ? null : onQtyRemove, icon: const Icon(Icons.remove), ), SizedBox( width: 35, height: 35, child: Center( child: Text('$cartItemQty', style: AppStyles.headLineText .copyWith(fontWeight: FontWeight.normal)), ), ), AppIconBtn( onPressed: onQtyAdd, icon: const Icon(Icons.add), ), const Spacer(), Text.rich(TextSpan( style: GoogleFonts.robotoMono( fontSize: 26, color: Pallete.darkBlue), text: '$productPrice ', children: [ TextSpan( text: ' FCFA', style: AppStyles.bodyText.copyWith(fontSize: 15), ) ])), ], ), ], ), ), const Divider(), Padding( padding: const EdgeInsets.fromLTRB(20, 0, 20, 20), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Contenu:', style: AppStyles.headLineText.copyWith( fontSize: 20, ), ), Text( style: const TextStyle(fontSize: 17), product[index] .description .toString() .substring(1, product[index].description.toString().length - 1), ), const Gap(10), Text( 'Additionnel:', style: AppStyles.headLineText.copyWith( fontSize: 20, ), ), Wrap( crossAxisAlignment: WrapCrossAlignment.center, children: [ Consumer( builder: (context, ref, child) { bool isAddonSelected = ref.watch(cartAddonProvider); return FilterChip( checkmarkColor: Pallete.orange, selected: isAddonSelected, onSelected: (value) { ref.read(cartAddonProvider.notifier).state = value; // 通过创建新列表更新状态,触发Riverpod检测 final addonNotifier = ref.read(cartAddonListProvider.notifier); if (value) { addonNotifier.state = [...addonNotifier.state, 'Meat']; } else { addonNotifier.state = addonNotifier.state.where((item) => item != 'Meat').toList(); } Log.error(addonNotifier.state.length); }, label: const Text('Viande'), ); }, ), ], ), Row(children: [ CircleAvatar( backgroundColor: Pallete.lightOrange, radius: 25, child: Icon( Icons.favorite_outline_rounded, color: Pallete.orange.withOpacity(0.5), ), ), const Gap(15), Expanded( child: AppButton( onPressed: () { addToCart(product[index].name, product[index].price, product[index].image, cartItemQty, addonList); }, text: 'Add to cart', ), ), ]) ]), ) ]); }, ); }); },
额外提示
- 对于列表、Map这类可变状态,Riverpod的
StateProvider必须通过替换整个对象来触发更新,直接修改对象内部内容不会触发UI重绘 - 所有影响UI展示的状态都需要被
Consumer或ref.watch监听,确保状态变化时能同步更新UI
内容的提问来源于stack exchange,提问作者Paolo068
相关产品推荐
相关产品推荐

