'Is it bad practice to store/translate static error strings in a form field model?
I'm building a Flutter app where I have a sign-up form that will throw an error if the user inputs an invalid email.
I have the following model for the email form field (using formz package):
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;
}
}
This makes things very simple for now within my widgets when I want to conditionally show error messages under a TextFormField(), but this seems to me like bad practice when considering how localization (which I would like to add to the app later) in flutter would require me to use the BuildContext to build the error strings instead. Like so:
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;
}
Given that I'd need to instantiate a BuildContext and configure it with my localization class just to be able to test this form field model, I think that there is poor separation of concerns here.
I thought it would be a good idea at first to generate error texts for my form field models within the class itself. This was primarily to avoid repeated code. But after localization became a nice-to-have for my app, this became a problem, and I'm running into analysis paralysis reading/considering different options for spearating the static string generation from the model class while avoiding my original problem of repeated code.
Any ideas would be much appreciated here.
Solution 1:[1]
I worked out a simple solution eventually and since I didn't get any viable answers, here is my solution. It would be nice if anyone could validate or give feedback to further improve it.
I basically created a new "common" widget to simply render a TextFormField based on the given EmailField value. If the current value has any errors, it will render an appropriate error string based on the error type:
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,
),
);
}
}
I've simplified the snippet to be concise and to the point. You can take a look at the actual implementation here.
Once I start adding localization, it will require me to change the _errorText getter read the static localized strings from the BuildContext, but that's still code that drives UI behavior and I maintain separation of concerns.
Regardless, this solution solves my main problems of:
- Unnecessary complexity in testing. Now I don't need to set up a UI
BuildContextto be able to test field models once I add localization. - Repeated code. I can just reuse this common widget wherever else I need.
- Separation of concerns. Code that drives specifically UI/View behavior is nicely separated from code that drives Model behavior. This in-turn simplifies testing.
Solution 2:[2]
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | H.Rahimy |
| Solution 2 | flutroid |
