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

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逻辑保持不变
              ),
            );
          },
        ),
      )
      

通用空错误排查方法

  • 全局搜索代码中所有的!空检查运算符,逐个确认对应变量在调用时机是否可能为空
  • 开启Flutter调试模式的异常捕获,报错堆栈会明确标注出错的代码行号,可直接定位问题点
  • 不要在build方法中执行修改控制器、更新状态这类带副作用的操作,build方法可能被框架频繁触发,极易产生时序问题

内容的提问来源于stack exchange,提问作者Max

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.02 02:46:12