Flutter点击键盘Done按钮触发Null check operator空值报错
Flutter TextFormField 点击Done键报空检查错误修复方案
问题表现
TextFormField完成输入后点击键盘Done按键,程序抛出Null check operator used on a null value错误,输入数据实际已正常保存,无法直接定位空值来源。
关联问题代码
class PriceCounter extends StatefulWidget { PriceCounter({Key? key, required this.price, this.onChanged}) : super(key: key); double price; final Function(double)? onChanged; @override State<PriceCounter> createState() => _PriceCounterState(); } class _PriceCounterState extends State<PriceCounter> { final _priceController = TextEditingController(); @override void dispose() { _priceController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final FilterPriceCubit cubit = BlocProvider.of<FilterPriceCubit>(context); return BlocBuilder<FilterPriceCubit, FilterPriceState>( builder: (context, state) { _priceController.text = state.fitlerPrice.toStringAsFixed(2).toString(); 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), SizedBox( width: 100, child: TextFormField( keyboardType: TextInputType.number, controller: _priceController, style: constants.Styles.normalBookTextStyleWhite, textAlign: TextAlign.center, decoration: const InputDecoration( prefix: Text('JC', style: constants.Styles.normalBookTextStyleWhite), suffix: Text( 'KWh', style: constants.Styles.smallerBookTextStyleWhite, ), contentPadding: EdgeInsets.zero, border: InputBorder.none, ), onFieldSubmitted: (value) { cubit.setPrice(value); widget.onChanged!(double.parse(value)); },
根因定位
代码中存在两处明确的空安全风险和时序问题,报错是二者共同导致:
- 第一处风险:
onFieldSubmitted回调中使用widget.onChanged!强制解引用可空回调。onChanged参数定义为可空类型Function(double)?,如果上层组件使用PriceCounter时未传入该回调,强制解引用会直接抛出空检查错误。结合“数据已正常保存”的现象,cubit.setPrice(value)已经执行完成,报错恰好发生在后续调用widget.onChanged!的时机,是最高概率的报错点。 - 第二处风险:在
BlocBuilder的builder方法中直接修改_priceController.text。builder方法会在状态更新时频繁触发,直接覆盖输入框控制器文本会和用户输入、键盘提交的时序冲突,当点击Done后键盘收起、输入框状态更新和组件重建时序错位时,也可能触发Flutter框架内部的空检查异常。
修复步骤
- 移除强制空检查,对可空回调做安全调用
替换原有onFieldSubmitted逻辑,增加输入合法性校验,避免parse失败和空回调报错:onFieldSubmitted: (value) { final double? inputPrice = double.tryParse(value); // 输入非法直接返回,避免parse报错 if (inputPrice == null) return; cubit.setPrice(value); // 安全调用,传入回调才执行 widget.onChanged?.call(inputPrice); }, - 修正控制器赋值逻辑,禁止在build方法中执行带副作用的修改操作
- 初始化时给控制器赋初值,不要每次build都覆盖文本:
@override void initState() { super.initState(); _priceController.text = widget.price.toStringAsFixed(2); } - 如果需要同步Bloc状态到输入框,使用
BlocListener单独监听状态变化,仅当文本和当前输入不一致时才更新控制器,避免打断用户输入、造成时序冲突:// 用BlocListener包裹原有BlocBuilder BlocListener<FilterPriceCubit, FilterPriceState>( listener: (context, state) { final targetText = state.fitlerPrice.toStringAsFixed(2); if (_priceController.text != targetText) { _priceController.text = targetText; // 更新文本后将光标移动到末尾 _priceController.selection = TextSelection.fromPosition( TextPosition(offset: _priceController.text.length) ); } }, child: BlocBuilder<FilterPriceCubit, FilterPriceState>( builder: (context, state) { // 删除原有在builder内给_priceController.text赋值的代码 return Padding( padding: const EdgeInsets.symmetric(horizontal: 21), child: Row( // 原有Row下的UI逻辑保持不变 ), ); }, ), )
- 初始化时给控制器赋初值,不要每次build都覆盖文本:
通用空错误排查方法
- 全局搜索代码中所有的
!空检查运算符,逐个确认对应变量在调用时机是否可能为空 - 开启Flutter调试模式的异常捕获,报错堆栈会明确标注出错的代码行号,可直接定位问题点
- 不要在build方法中执行修改控制器、更新状态这类带副作用的操作,build方法可能被框架频繁触发,极易产生时序问题
内容的提问来源于stack exchange,提问作者Max
相关产品推荐
相关产品推荐

