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

Flutter中TextFormField清空输入触发Invalid number异常的解决咨询

问题描述

在Flutter开发中,实现了用户输入数值时的校验逻辑:当销售价格输入框的数值大于原价输入框数值时,弹出SnackBar提示。但清空输入框内容时,会抛出FormatException: Invalid number异常。

异常详情

══╡ EXCEPTION CAUGHT BY WIDGETS ╞═══════════════════════════════════════════════════════════════════
The following FormatException was thrown while calling onChanged:
Invalid number (at character 1)

^

When the exception was thrown, this was the stack:
#0      int._handleFormatError (dart:core-patch/integers_patch.dart:129:7)
#1      int.parse (dart:core-patch/integers_patch.dart:55:14)
#2      _GeneralTabState.build.<anonymous closure>.<anonymous closure>
(package:shop_app_vendor/screens/add_products/general_tab.dart:247:25)
#3      new TextFormField.<anonymous closure>.onChangedHandler (package:flutter/src/material/text_form_field.dart:188:25)
#4      EditableTextState._formatAndSetValue (package:flutter/src/widgets/editable_text.dart:2630:27)
#5      EditableTextState.userUpdateTextEditingValue (package:flutter/src/widgets/editable_text.dart:2919:5)
#6      EditableTextState._replaceText (package:flutter/src/widgets/editable_text.dart:3144:5)
#7      CallbackAction.invoke (package:flutter/src/widgets/actions.dart:534:39)
#8      ActionDispatcher.invokeAction (package:flutter/src/widgets/actions.dart:573:21)
#9      Actions.invoke.<anonymous closure> (package:flutter/src/widgets/actions.dart:872:48)
#10     Actions._visitActionsAncestors (package:flutter/src/widgets/actions.dart:653:18)
#11     Actions.invoke (package:flutter/src/widgets/actions.dart:866:30)
#12     _DeleteTextAction.invoke (package:flutter/src/widgets/editable_text.dart:4042:20)
#13     _OverridableContextAction.invokeDefaultAction (package:flutter/src/widgets/actions.dart:1696:28)
#14     _OverridableActionMixin.invoke (package:flutter/src/widgets/actions.dart:1559:9)
#15     ActionDispatcher.invokeAction (package:flutter/src/widgets/actions.dart:571:21)
#16     ShortcutManager.handleKeypress (package:flutter/src/widgets/shortcuts.dart:755:38)
#17     _ShortcutsState._handleOnKey (package:flutter/src/widgets/shortcuts.dart:956:20)
#18     FocusManager._handleKeyMessage (package:flutter/src/widgets/focus_manager.dart:1687:32)
#19     KeyEventManager._dispatchKeyMessage (package:flutter/src/services/hardware_keyboard.dart:828:34)
#20     KeyEventManager.handleRawKeyMessage (package:flutter/src/services/hardware_keyboard.dart:875:15)
#21     BasicMessageChannel.setMessageHandler.<anonymous closure> (package:flutter/src/services/platform_channel.dart:77:49)      
#22     BasicMessageChannel.setMessageHandler.<anonymous closure> (package:flutter/src/services/platform_channel.dart:76:47)      
#23     _DefaultBinaryMessenger.setMessageHandler.<anonymous closure> (package:flutter/src/services/binding.dart:380:35)
#24     _DefaultBinaryMessenger.setMessageHandler.<anonymous closure> (package:flutter/src/services/binding.dart:377:46)
#25     _invoke2.<anonymous closure> (dart:ui/hooks.dart:190:15)
#29     _invoke2 (dart:ui/hooks.dart:189:10)
#30     _ChannelCallbackRecord.invoke (dart:ui/channel_buffers.dart:42:5)
#31     _Channel.push (dart:ui/channel_buffers.dart:132:31)
#32     ChannelBuffers.push (dart:ui/channel_buffers.dart:329:17)
#33     PlatformDispatcher._dispatchPlatformMessage (dart:ui/platform_dispatcher.dart:589:22)
#34     _dispatchPlatformMessage (dart:ui/hooks.dart:89:31)
(elided 3 frames from dart:async)
════════════════════════════════════════════════════════════════════════════════════════════════════

相关代码

scf.dart

class SCF {
  scaffoldMsg({context, msg}) {
    ScaffoldMessenger.of(context).showSnackBar(SnackBar(
      content: Text(msg),
      backgroundColor: Colors.red,
      action: SnackBarAction(
        label: 'OK',
        textColor: Colors.white,
        onPressed: () {
          ScaffoldMessenger.of(context).clearSnackBars();
        },
      ),
    ));
  }
}

general_tab.dart

class GeneralTab extends StatefulWidget {
  const GeneralTab({Key? key}) : super(key: key);

  @override
  State<GeneralTab> createState() => _GeneralTabState();
}

class _GeneralTabState extends State<GeneralTab> {
  final SCF _scf = SCF();

  @override
  Widget build(BuildContext context) {
    return Consumer<ProductProvider>(
      builder: (context, provider, child) {
        return ListView(
          padding: const EdgeInsets.all(15.0),
          children: [
            // Regular Price
            FormFieldInput(
              label: 'Regular Price',
              inputType: TextInputType.number,
              onChanged: (value) {
                provider.getFormData(regularPrice: int.parse(value));
              },
            ),
            // Sales Price
            FormFieldInput(
              label: 'Sales Price',
              inputType: TextInputType.number,
              onChanged: (value) {
                if (int.parse(value) > provider.productData!['regularPrice']) {
                  _scf.scaffoldMsg(
                    context: context,
                    msg: 'Sales price should be less than regular price',
                  );
                }
                setState(() {
                  provider.getFormData(salesPrice: int.parse(value));
                });
              },
            ),
          ],
        );
      },
    );
  }
}

product_provider.dart

class ProductProvider with ChangeNotifier {
  Map<String, dynamic>? productData = {};

  getFormData({
    int? regularPrice,
    int? salesPrice,
  }) {
    if (regularPrice != null) {
      productData!['regularPrice'] = regularPrice;
    }

    if (salesPrice != null) {
      productData!['salesPrice'] = salesPrice;
    }

    notifyListeners();
  }
}

form_field.dart

class FormFieldInput extends StatelessWidget {
  final String? label;
  final void Function(String)? onChanged;
  final TextInputType? inputType;

  const FormFieldInput({
    Key? key,
    this.label,
    this.onChanged,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return TextFormField(
      decoration: InputDecoration(
        label: Text(label!),
      ),
      onChanged: onChanged,
    );
  }
}

修复方案

问题根源是清空输入框时value为空字符串,调用int.parse('')会抛出格式异常。需要在解析前先校验输入内容是否有效,同时处理空值和非数值情况。

步骤1:添加安全的数值解析工具方法

封装一个工具方法,处理空字符串和无效数值的情况,返回可空的整数:

int? tryParseInt(String? value) {
  if (value == null || value.trim().isEmpty) return null;
  return int.tryParse(value.trim());
}

步骤2:修改输入框的onChanged逻辑

在general_tab.dart中使用上面的工具方法,避免直接调用int.parse:

// Regular Price的onChanged修改为
onChanged: (value) {
  final price = tryParseInt(value);
  provider.getFormData(regularPrice: price);
},

// Sales Price的onChanged修改为
onChanged: (value) {
  final salesPrice = tryParseInt(value);
  final regularPrice = provider.productData?['regularPrice'];
  // 只有当两个价格都不为空时才进行校验
  if (salesPrice != null && regularPrice != null && salesPrice > regularPrice) {
    _scf.scaffoldMsg(
      context: context,
      msg: 'Sales price should be less than regular price',
    );
  }
  provider.getFormData(salesPrice: salesPrice);
  // 这里不需要setState,因为provider的notifyListeners会触发Consumer重建
},

步骤3:调整Provider的逻辑(可选)

如果允许价格为空,可以保持现有逻辑;如果需要默认值,可在getFormData中处理:

// product_provider.dart中修改getFormData
getFormData({
  int? regularPrice,
  int? salesPrice,
}) {
  if (regularPrice != null) {
    productData!['regularPrice'] = regularPrice;
  } else {
    // 可选:清空或设置默认值
    productData!.remove('regularPrice');
    // 或者 productData!['regularPrice'] = 0;
  }

  if (salesPrice != null) {
    productData!['salesPrice'] = salesPrice;
  } else {
    productData!.remove('salesPrice');
    // 或者 productData!['salesPrice'] = 0;
  }

  notifyListeners();
}

步骤4:完善FormFieldInput(可选)

在form_field.dart中启用数字键盘,避免用户输入非数字字符:

@override
Widget build(BuildContext context) {
  return TextFormField(
    decoration: InputDecoration(
      label: Text(label!),
    ),
    keyboardType: inputType ?? TextInputType.text, // 应用传入的键盘类型
    inputFormatters: inputType == TextInputType.number 
        ? [FilteringTextInputFormatter.digitsOnly] // 只允许输入数字
        : null,
    onChanged: onChanged,
  );
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 18:55:34