Flutter Provider异常:添加商品不触发UI更新,删除时刷新正常
问题原因
- 你在
build方法中获取AllGoodsViewModel实例时写了listen: false,这会直接让当前页面放弃监听ViewModel的notifyListeners()通知,ViewModel里的属性修改后,不会主动触发页面重建。 - 删除操作看着能正常刷新,根本不是Provider通知生效,是你调用
changeButtonState的代码被包在了setState()里面,setState会强制页面重新执行build,自然会读到最新的isEmpty值,所以状态切换看起来正常。 - 添加商品、增减商品数量走的是
haveItem方法,这个方法里调用changeButtonState的时候没有包裹setState,本来指望notifyListeners触发UI更新,但是之前已经关闭了监听,页面根本收不到更新信号,当然不会刷新按钮状态。 - 额外隐患:你现在的状态逻辑非常混乱,既在ViewModel里改状态,又在UI层的
setState里直接修改购物车数据、操作本地数据库,状态来源不统一,就算这次问题修好了,后续也很容易出现其他UI和数据不同步的问题。
修复方法
- 打开Provider的监听,两种实现选其一即可:要么把获取model代码里的
listen: false改成listen: true,更推荐用Consumer只包裹底部需要刷新的按钮区域,不用整页跟着重建,性能更好。 - 把所有修改状态、操作数据的逻辑全部收拢到ViewModel中,UI层只负责点击按钮时调用VM的对应方法、根据VM返回的状态渲染界面,不要在UI层的
setState里直接改购物车数据、操作数据库,保证状态只有VM这一个修改入口。
核心代码修正示例
ViewModel层调整
把所有购物车操作逻辑移到ViewModel内,UI层不再直接处理数据:
class AllGoodsViewModel extends ChangeNotifier { var _isEmpty = true; bool get isEmpty => _isEmpty; void detailSetting(Goods item) { final ind = cart.value.indexWhere((element) => element.id == item.id); _isEmpty = ind == -1; notifyListeners(); } void changeButtonState(bool state) { _isEmpty = state; notifyListeners(); } Future<void> haveItem({required Goods item, required int operation}) async { final ind = cart.value.indexWhere((element) => element.id == item.id); if (ind == -1) { final minCount = item.optState == 0 ? 1 : item.opt!.count; if (item.count < minCount) return; changeButtonState(false); cart.value.add(item); final newInd = cart.value.length - 1; cart.value[newInd].isOpt = item.optState == 0 ? false : true; cart.value[newInd].orderCount = minCount; cart.value = List.from(cart.value); await SQFliteService.cart.addToCart(cart.value[newInd]); changeCountInCart(operation); } else { final count = cart.value[ind].orderCount; if (count > item.count) return; if (operation >=0 && count + operation > item.count) return; // 减到最低阈值时直接切换为空状态 if (operation < 0 && (count + operation) <= (item.optState == 0 ? 1 : item.opt!.count)) { changeButtonState(true); cart.value[ind].orderCount = 0; await SQFliteService.cart.delete(cart.value[ind].id); cart.value = List.from(cart.value)..removeAt(ind); changeCountInCart(operation); return; } changeButtonState(false); cart.value[ind].orderCount += operation; cart.value = List.from(cart.value); await SQFliteService.cart.updateItem(cart.value[ind].id, {"orderCount":cart.value[ind].orderCount}); changeCountInCart(operation); } } }
UI层调整
去掉UI层直接操作数据、setState包裹VM调用的逻辑,用Consumer监听状态刷新:
@override Widget build(BuildContext context) { return ScreenUtilInitService().build((context) => Scaffold( backgroundColor: Colors.white, body: Container( height: 64.h, color: Colors.white, // 仅监听包裹范围内的组件刷新 child: Consumer<AllGoodsViewModel>( builder: (context, model, child) { return model.isEmpty ? noInCart(model) : inCart(model); }, ), ), )); } Widget inCart(AllGoodsViewModel model){ return GestureDetector( onPanDown: (details) async { Goods item = widget.item; RenderBox _cardBox = _key.currentContext!.findRenderObject() as RenderBox; final localDx = details.localPosition.dx; if (localDx <= _cardBox.size.width/2) { Goods value = cart.value.firstWhere((element) => element.id == item.id); // 所有操作统一走VM方法,UI层不直接处理数据 await model.haveItem( item: item, operation: item.optState == 0 ? -1 : (-1 * value.opt!.count) ); } else { await model.haveItem( item: item, operation: item.optState == 0 ? 1 : item.count ); } }, child: /* 原inCart组件内容 */ ); } Widget noInCart(AllGoodsViewModel model){ return Container( width: size.width - 16.w, margin: EdgeInsets.symmetric(vertical: 10.h), key: _key, child: TextButton( style: /* 原按钮样式 */, onPressed: (){ model.haveItem(item: widget.item, operation: widget.item.optState == 0 ? 1 : widget.item.count); }, child: /* 原noInCart组件内容 */ ), ); }
内容的提问来源于stack exchange,提问作者Vasya2014
相关产品推荐
相关产品推荐

