Flutter点击按钮时Bloc实现的价格计数器数值不更新问题
问题描述
基于Bloc状态管理实现带价格展示、增减按钮的计数器功能,运行时点击增减按钮后价格数值无变化,判定为状态更新未触发UI联动,需定位原因并修复。
涉及两个核心组件:price_counter、input_fields_price,原始实现代码如下:
price_counter 原始代码
class PriceCounter extends StatelessWidget { PriceCounter({Key? key, required this.price}) : super(key: key); double price = 0.13; @override Widget build(BuildContext context) { final FilterPriceCubit cubit = BlocProvider.of<FilterPriceCubit>(context); return BlocBuilder<FilterPriceCubit, FilterPriceState>( builder: (context, state) { if (state is FilterPriceInitial) { state.fitlerPrice = price; return Padding( padding: const EdgeInsets.symmetric(horizontal: 21), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ IconButton( onPressed: () => cubit.priceFilterDecrement(state.fitlerPrice), icon: SvgPicture.asset(constants.Assets.minus), constraints: const BoxConstraints(), padding: EdgeInsets.zero, ), const SizedBox(width: 20), InputFieldPrice( price: state.fitlerPrice, textStyle: constants.Styles.normalBookTextStyleWhite), const SizedBox(width: 20), IconButton( onPressed: () => cubit.priceFilterIncrement(state.fitlerPrice), icon: SvgPicture.asset(constants.Assets.plus), constraints: const BoxConstraints(), padding: EdgeInsets.zero, ), ], ), ); } return const SizedBox(); }, ); } }
input_fields_price 原始代码
class InputFieldPrice extends StatefulWidget { final double price; final TextStyle textStyle; const InputFieldPrice( {Key? key, required this.price, required this.textStyle}) : super(key: key); @override State<InputFieldPrice> createState() => _InputFieldPrice(); } class _InputFieldPrice extends State<InputFieldPrice> { final _formKey = GlobalKey<FormState>(); final _priceController = TextEditingController(); @override void dispose() { _priceController.dispose(); super.dispose(); } @override Widget build(BuildContext context) => SizedBox( width: 100, child: Form( key: _formKey, child: TextFormField( keyboardType: TextInputType.number, controller: _priceController ..text = widget.price.toStringAsFixed(2), style: widget.textStyle, textAlign: TextAlign.center, decoration: InputDecoration( prefix: Text('JC', style: widget.textStyle), suffix: const Text( 'KWh', style: constants.Styles.smallerBookTextStyleWhite, ), contentPadding: EdgeInsets.zero, border: InputBorder.none, ), inputFormatters: [ LengthLimitingTextInputFormatter(4), FilteringTextInputFormatter.allow(RegExp("[0-9.]")), ], ), ), ); }
故障根因
- 直接修改状态实例属性:Bloc要求状态为不可变类型,代码在
BlocBuilder构建流程中直接修改FilterPriceInitial对象的fitlerPrice字段,没有通过emit方法提交新状态,Bloc无法感知状态变更,不会触发UI重建。 - 状态分支判断错误:
BlocBuilder仅在状态为FilterPriceInitial类型时返回计数器UI,一旦Cubit提交其他类型的状态,会直接返回空SizedBox,计数器整体消失;即使提交同类型状态,因为是同一对象引用,Bloc默认的相等性校验会判定状态无变化,跳过重建流程。 - 增减逻辑耦合UI层:按钮点击时将当前状态的价格值传入Cubit增减方法,Cubit没有独立维护状态值,计算逻辑和UI层强耦合,极易出现值传递错误。
- 输入框同步逻辑错误:
InputFieldPrice为StatefulWidget,在build方法中反复给TextEditingController赋值,会导致光标位置异常、父组件传入新价格时无法正确同步展示内容。
修复方案
按以下顺序修改代码即可实现价格正常联动:
- 重构Bloc状态与Cubit逻辑
将状态类改为不可变类型,建议配合equatable做值相等性判断,Cubit内部独立维护价格计算逻辑,增减操作后提交全新状态实例:
// 状态基类,继承Equatable保证值比较正确 class FilterPriceState extends Equatable { final double filterPrice; const FilterPriceState({required this.filterPrice}); @override List<Object> get props => [filterPrice]; } // 初始状态 class FilterPriceInitial extends FilterPriceState { const FilterPriceInitial({required double initialPrice}) : super(filterPrice: initialPrice); } // Cubit实现 class FilterPriceCubit extends Cubit<FilterPriceState> { FilterPriceCubit({double defaultPrice = 0.13}) : super(FilterPriceInitial(initialPrice: defaultPrice)); // 减价逻辑,步长默认0.01,做边界校验防止价格为负 void priceFilterDecrement() { final newPrice = (state.filterPrice - 0.01).clamp(0, double.infinity); emit(FilterPriceState(filterPrice: newPrice)); } // 加价逻辑 void priceFilterIncrement() { final newPrice = state.filterPrice + 0.01; emit(FilterPriceState(filterPrice: newPrice)); } }
- 修正PriceCounter组件
移除直接修改状态属性的代码,删除错误的空容器返回分支,按钮调用增减方法时不需要传参:
class PriceCounter extends StatelessWidget { const PriceCounter({Key? key, required this.price}) : super(key: key); final double price; @override Widget build(BuildContext context) { final FilterPriceCubit cubit = BlocProvider.of<FilterPriceCubit>(context); return BlocBuilder<FilterPriceCubit, FilterPriceState>( builder: (context, state) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 21), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ IconButton( onPressed: () => cubit.priceFilterDecrement(), icon: SvgPicture.asset(constants.Assets.minus), constraints: const BoxConstraints(), padding: EdgeInsets.zero, ), const SizedBox(width: 20), InputFieldPrice( price: state.filterPrice, textStyle: constants.Styles.normalBookTextStyleWhite), const SizedBox(width: 20), IconButton( onPressed: () => cubit.priceFilterIncrement(), icon: SvgPicture.asset(constants.Assets.plus), constraints: const BoxConstraints(), padding: EdgeInsets.zero, ), ], ), ); }, ); } }
- 修正InputFieldPrice同步逻辑
移除build方法中直接给controller赋值的代码,通过initState做初始化赋值,通过didUpdateWidget生命周期监听父组件传入的价格变化,同步更新输入框内容:
class InputFieldPrice extends StatefulWidget { final double price; final TextStyle textStyle; const InputFieldPrice( {Key? key, required this.price, required this.textStyle}) : super(key: key); @override State<InputFieldPrice> createState() => _InputFieldPriceState(); } class _InputFieldPriceState extends State<InputFieldPrice> { final _formKey = GlobalKey<FormState>(); final _priceController = TextEditingController(); @override void initState() { super.initState(); _priceController.text = widget.price.toStringAsFixed(2); } @override void didUpdateWidget(covariant InputFieldPrice oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.price != widget.price) { _priceController.text = widget.price.toStringAsFixed(2); // 同步光标位置到文本末尾 _priceController.selection = TextSelection.fromPosition( TextPosition(offset: _priceController.text.length) ); } } @override void dispose() { _priceController.dispose(); super.dispose(); } @override Widget build(BuildContext context) => SizedBox( width: 100, child: Form( key: _formKey, child: TextFormField( keyboardType: TextInputType.number, controller: _priceController, style: widget.textStyle, textAlign: TextAlign.center, decoration: InputDecoration( prefix: Text('JC', style: widget.textStyle), suffix: const Text( 'KWh', style: constants.Styles.smallerBookTextStyleWhite, ), contentPadding: EdgeInsets.zero, border: InputBorder.none, ), inputFormatters: [ LengthLimitingTextInputFormatter(4), FilteringTextInputFormatter.allow(RegExp("[0-9.]")), ], ), ), ); }
验证标准
- 点击加减按钮时,Cubit会提交全新的状态实例,状态类props包含价格字段,Bloc能正确识别状态变化触发UI重建
- 输入框内容和状态值实时同步,不会出现展示值和实际状态不一致、光标错位问题
- 状态更新后计数器UI不会消失,价格数值随按钮点击正常增减
- 价格减到0时不会出现负数,边界逻辑正常
内容的提问来源于stack exchange,提问作者Max
相关产品推荐
相关产品推荐

