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

Riverpod 2在showModalBottomSheet中无响应,如何实现实时状态更新?

Riverpod 2配合showModalBottomSheet状态更新问题

问题描述

使用Riverpod 2管理Flutter外卖应用状态时,在showModalBottomSheet(简称sMBS)中遇到状态更新异常:

  • 已通过Consumer实现商品数量增减的实时UI更新
  • 点击FilterChip添加/移除附加选项时,价格不会实时变化,必须关闭并重新打开sMBS才能看到更新

核心问题分析

  1. 未监听附加选项列表状态:计算商品总价时没有监听cartAddonListProvider的变化,导致附加选项变更时无法触发UI重绘
  2. 错误的列表更新方式:直接调用列表的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 16:15:15