Flutter Bloc开发中如何向TextField传值并回传更新数据
Flutter Bloc 场景下TextField与状态双向同步实现方案
问题核心
当前实现存在3个断点导致双向同步失效:
- 数据库读取的
state.mapFilter.maxPrice未作为初始值注入CounterCubit,计数器初始状态和数据源不一致 TextEditingController文本赋值逻辑写在build方法中,会在组件重建时覆盖用户手动输入的内容- 输入框修改、计数器增减的数值未回传至上层MapFilter状态,无法完成数据源同步
分步修复代码
1. 调整CounterCubit逻辑
给Cubit增加初始值入参、手动设置价格的事件,增减逻辑基于当前状态计算:
class CounterCubit extends Cubit<CounterState> { // 支持传入初始价格,默认0 CounterCubit({double initialPrice = 0}) : super(CounterState(priceFilterCountValue: initialPrice)); // 递增逻辑,可自行调整步长、添加最大值限制 void priceFilterIncrement() { final newPrice = state.priceFilterCountValue + 0.1; emit(state.copyWith(priceFilterCountValue: double.parse(newPrice.toStringAsFixed(2)))); } // 递减逻辑,限制最小值为0 void priceFilterDecrement() { if (state.priceFilterCountValue <= 0) return; final newPrice = state.priceFilterCountValue - 0.1; emit(state.copyWith(priceFilterCountValue: double.parse(newPrice.toStringAsFixed(2)))); } // 手动设置价格(供输入框调用) void setPrice(double price) { if (price < 0) price = 0; emit(state.copyWith(priceFilterCountValue: double.parse(price.toStringAsFixed(2)))); } } // 配套CounterState建议用equatable做值比较 class CounterState extends Equatable { final double priceFilterCountValue; const CounterState({required this.priceFilterCountValue}); CounterState copyWith({double? priceFilterCountValue}) { return CounterState( priceFilterCountValue: priceFilterCountValue ?? this.priceFilterCountValue, ); } @override List<Object> get props => [priceFilterCountValue]; }
2. 修改入口与PriceCounter组件
在入口创建Cubit时传入数据库读取的初始值,同时监听Cubit状态变化同步更新全局MapFilter状态;PriceCounter组件移除无用的内部可变price变量,透传变更回调:
// main入口逻辑 BlocProvider( create: (context) => CounterCubit(initialPrice: state.mapFilter.maxPrice), child: PriceCounter( onPriceChanged: (newPrice) { // 调用对应Bloc事件更新mapFilter.maxPrice,同步到数据库 context.read<MapFilterBloc>().add(UpdateMaxPriceEvent(maxPrice: newPrice)); }, ), ) // PriceCounter组件 class PriceCounter extends StatelessWidget { final Function(double) onPriceChanged; const PriceCounter({Key? key, required this.onPriceChanged}) : super(key: key); @override Widget build(BuildContext context) { final CounterCubit cubit = BlocProvider.of<CounterCubit>(context); 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), BlocConsumer<CounterCubit, CounterState>( listener: (context, state) { // 状态变化时透传给上层同步全局状态 onPriceChanged(state.priceFilterCountValue); }, builder: (context, state) => InputFieldPrice( price: state.priceFilterCountValue, textStyle: constants.Styles.normalBookTextStyleWhite, onChanged: (price) => cubit.setPrice(price), ), ), const SizedBox(width: 20), IconButton( onPressed: () => cubit.priceFilterIncrement(), icon: SvgPicture.asset(constants.Assets.plus), constraints: const BoxConstraints(), padding: EdgeInsets.zero, ), ], ), ); } }
3. 修复InputFieldPrice输入框组件
将controller赋值逻辑从build方法移至didUpdateWidget,仅当外部传入price变化时才更新文本,避免覆盖用户输入;打开onChanged回调做数值合法性校验:
class InputFieldPrice extends StatefulWidget { final double price; final TextStyle textStyle; final Function(double)? onChanged; const InputFieldPrice( {Key? key, required this.price, required this.textStyle, this.onChanged}) : super(key: key); @override State<InputFieldPrice> createState() => _InputFieldPrice(); } class _InputFieldPrice 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); // 仅当外部传入的price和当前输入值不一致时(点击增减按钮、外部状态更新场景)才更新文本 final currentInputValue = double.tryParse(_priceController.text) ?? 0; if (currentInputValue != widget.price) { // 同步文本同时保留光标位置,避免输入跳位 final newText = widget.price.toStringAsFixed(2); _priceController.value = _priceController.value.copyWith( text: newText, selection: TextSelection.collapsed(offset: newText.length), ); } } @override void dispose() { _priceController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return SizedBox( width: 100, child: Form( key: _formKey, child: TextFormField( keyboardType: const TextInputType.numberWithOptions(decimal: true), 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, ), onChanged: (value) { final price = double.tryParse(value); if (price != null && widget.onChanged != null) { widget.onChanged!(price); } }, ), ), ); } }
同步链路说明
- 初始化阶段:数据库读取的
maxPrice直接作为CounterCubit初始状态,输入框首次渲染直接展示该值 - 增减按钮操作:点击按钮触发Cubit数值变更→BlocConsumer重建输入框更新文本→listener触发上层回调更新全局
mapFilter.maxPrice - 手动输入操作:输入框合法数值通过onChanged回调触发Cubit更新状态→listener触发上层回调同步全局状态
- 外部全局状态更新:如果其他逻辑修改了
mapFilter.maxPrice,只需触发Cubit的setPrice事件,即可自动同步到输入框展示
注意:监听全局mapFilter状态时建议做防抖处理,避免输入过程中频繁触发数据库写入。
内容的提问来源于stack exchange,提问作者Max
相关产品推荐
相关产品推荐

