store/translate 表单字段模型中的静态错误字符串是不好的做法吗?

Is it bad practice to store/translate static error strings in a form field model?

我正在构建一个 Flutter 应用,其中有一个注册表单,如果用户输入无效的电子邮件,该表单会抛出错误。

我有以下电子邮件表单字段模型(使用 formz 包):

enum EmailFieldValidationError { empty, invalid }

class EmailField extends FormzInput<String, EmailFieldValidationError> {
  const EmailField.pure() : super.pure('');

  const EmailField.dirty([String value = '']) : super.dirty(value);

  static final RegExp _emailRegExp = RegExp(
    r'^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$',
  );

  @override
  EmailFieldValidationError? validator(String? value) {
  }

  String? get errorText {
    if (error != null && !pure && value.isNotEmpty) {
      switch (error) {
        case EmailFieldValidationError.invalid:
          return 'Invalid email.';
        default:
          return null;
      }
    }
    return null;
  }
}

当我想在 TextFormField() 下有条件地显示错误消息时,这使我的小部件中的事情变得非常简单,但在考虑如何本地化(我想添加到应用程序)在 flutter 中需要我使用 BuildContextbuild 错误字符串。像这样:

  String? buildErrorText(BuildContext context) {
    if (error != null && !pure && value.isNotEmpty) {
      switch (error) {
        case EmailFieldValidationError.invalid:
          return AppLocalizations.of(context)!.emailFieldInvalidValueError;
        default:
          return null;
      }
    }
    return null;
  }

考虑到我需要实例化一个 BuildContext 并使用我的本地化 class 对其进行配置,以便能够测试此表单字段模型,我认为关注点分离很差这里。

我认为首先在 class 本身内为我的表单字段模型生成错误文本是个好主意。这主要是为了避免重复代码。但是在本地化成为我的应用程序的一个不错的选择之后,这成为了一个问题,我 运行 陷入分析瘫痪 reading/considering 从模型中分离静态字符串生成的不同选项 class同时避免了我原来的重复代码问题。

如有任何想法,我们将不胜感激。

显示基于区块状态的错误文本。

Example with Bloc package

我最终找到了一个简单的解决方案,但由于我没有得到任何可行的答案,所以这是我的解决方案。如果有人可以验证或提供反馈以进一步改进它,那就太好了。

我基本上创建了一个新的“通用”小部件,以根据给定的 EmailField 值简单地呈现 TextFormField。如果当前值有任何错误,它将根据错误类型呈现适当的错误字符串:

class CommonEmailFormField extends StatelessWidget {
  const CommonEmailFormField({
    Key? key,
    required this.email,
    this.onChanged,
  }) : super(key: key);

  final EmailField email;
  final Function(String)? onChanged;

  String? get _errorText {
    if (email.error != null && !email.pure && email.value.isNotEmpty) {
      switch (email.error) {
        case EmailFieldValidationError.invalid:
          return 'Invalid email.';
        default:
          return null;
      }
    }
    return null;
  }

  @override
  Widget build(BuildContext context) {
    return TextFormField(
      initialValue: email.value,
      onChanged: onChanged,
      decoration: InputDecoration(
        labelText: 'Email *',
        errorText: _errorText,
      ),
    );
  }
}

我已将代码段简化为简明扼要。你可以看看实际实现here.

一旦我开始添加本地化,​​它将要求我更改 _errorText getter 从 BuildContext 读取静态本地化字符串,但这仍然是驱动 [=38] 的代码=] 行为和我保持关注点分离。

无论如何,这个解决方案解决了我的主要问题:

  1. 测试中不必要的复杂性。现在我不需要设置 UI BuildContext 就可以在添加本地化后测试现场模型。
  2. 重复代码。我可以在任何需要的地方重复使用这个通用小部件。
  3. 关注点分离。专门驱动 UI/View 行为的代码与驱动模型行为的代码很好地分开。这 in-turn 简化了测试。