Resetting form shows error messages
abdullahkaracabey opened this issue · 4 comments
abdullahkaracabey commented
Is there an existing issue for this?
- I have searched the existing issues
Package/Plugin version
9.2.1
Platforms
- Android
- iOS
- Linux
- MacOS
- Web
- Windows
Flutter doctor
Flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.16.1, on macOS 14.2.1 23C71 darwin-arm64, locale en-TR)
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 15.0.1)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2022.3)
[✓] VS Code (version 1.86.1)
[✓] Connected device (5 available)
[✓] Network resources
• No issues found!
Minimal code example
Code sample
import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:form_builder_validators/form_builder_validators.dart';
import 'package:intl/intl.dart';
class CompleteForm extends StatefulWidget {
const CompleteForm({Key? key}) : super(key: key);
@override
State<CompleteForm> createState() {
return _CompleteFormState();
}
}
class _CompleteFormState extends State<CompleteForm> {
bool autoValidate = true;
bool readOnly = false;
bool showSegmentedControl = true;
final _formKey = GlobalKey<FormBuilderState>();
bool _ageHasError = false;
bool _genderHasError = false;
var genderOptions = ['Male', 'Female', 'Other'];
void _onChanged(dynamic val) => debugPrint(val.toString());
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
children: <Widget>[
FormBuilder(
key: _formKey,
// enabled: false,
onChanged: () {
_formKey.currentState!.save();
debugPrint(_formKey.currentState!.value.toString());
},
autovalidateMode: AutovalidateMode.disabled,
initialValue: const {
'movie_rating': 5,
'best_language': 'Dart',
'age': '13',
'gender': 'Male',
'languages_filter': ['Dart']
},
skipDisabled: true,
child: Column(
children: <Widget>[
const SizedBox(height: 15),
FormBuilderDateTimePicker(
name: 'date',
initialEntryMode: DatePickerEntryMode.calendar,
initialValue: DateTime.now(),
inputType: InputType.both,
decoration: InputDecoration(
labelText: 'Appointment Time',
suffixIcon: IconButton(
icon: const Icon(Icons.close),
onPressed: () {
_formKey.currentState!.fields['date']?.didChange(null);
},
),
),
initialTime: const TimeOfDay(hour: 8, minute: 0),
// locale: const Locale.fromSubtags(languageCode: 'fr'),
),
FormBuilderDateRangePicker(
name: 'date_range',
firstDate: DateTime(1970),
lastDate: DateTime(2030),
format: DateFormat('yyyy-MM-dd'),
onChanged: _onChanged,
decoration: InputDecoration(
labelText: 'Date Range',
helperText: 'Helper text',
hintText: 'Hint text',
suffixIcon: IconButton(
icon: const Icon(Icons.close),
onPressed: () {
_formKey.currentState!.fields['date_range']
?.didChange(null);
},
),
),
),
FormBuilderSlider(
name: 'slider',
validator: FormBuilderValidators.compose([
FormBuilderValidators.min(6),
]),
onChanged: _onChanged,
min: 0.0,
max: 10.0,
initialValue: 7.0,
divisions: 20,
activeColor: Colors.red,
inactiveColor: Colors.pink[100],
decoration: const InputDecoration(
labelText: 'Number of things',
),
),
FormBuilderRangeSlider(
name: 'range_slider',
onChanged: _onChanged,
min: 0.0,
max: 100.0,
initialValue: const RangeValues(4, 7),
divisions: 20,
maxValueWidget: (max) => TextButton(
onPressed: () {
_formKey.currentState?.patchValue(
{'range_slider': const RangeValues(4, 100)},
);
},
child: Text(max),
),
activeColor: Colors.red,
inactiveColor: Colors.pink[100],
decoration: const InputDecoration(labelText: 'Price Range'),
),
FormBuilderCheckbox(
name: 'accept_terms',
initialValue: false,
onChanged: _onChanged,
title: RichText(
text: const TextSpan(
children: [
TextSpan(
text: 'I have read and agree to the ',
style: TextStyle(color: Colors.black),
),
TextSpan(
text: 'Terms and Conditions',
style: TextStyle(color: Colors.blue),
// Flutter doesn't allow a button inside a button
// https://github.com/flutter/flutter/issues/31437#issuecomment-492411086
/*
recognizer: TapGestureRecognizer()
..onTap = () {
print('launch url');
},
*/
),
],
),
),
validator: FormBuilderValidators.equal(
true,
errorText:
'You must accept terms and conditions to continue',
),
),
FormBuilderTextField(
autovalidateMode: AutovalidateMode.always,
name: 'age',
decoration: InputDecoration(
labelText: 'Age',
suffixIcon: _ageHasError
? const Icon(Icons.error, color: Colors.red)
: const Icon(Icons.check, color: Colors.green),
),
onChanged: (val) {
setState(() {
_ageHasError =
!(_formKey.currentState?.fields['age']?.validate() ??
false);
});
},
// valueTransformer: (text) => num.tryParse(text),
validator: FormBuilderValidators.compose([
FormBuilderValidators.required(),
FormBuilderValidators.numeric(),
FormBuilderValidators.max(70),
]),
// initialValue: '12',
keyboardType: TextInputType.number,
textInputAction: TextInputAction.next,
),
FormBuilderDropdown<String>(
name: 'gender',
decoration: InputDecoration(
labelText: 'Gender',
suffix: _genderHasError
? const Icon(Icons.error)
: const Icon(Icons.check),
hintText: 'Select Gender',
),
validator: FormBuilderValidators.compose(
[FormBuilderValidators.required()]),
items: genderOptions
.map((gender) => DropdownMenuItem(
alignment: AlignmentDirectional.center,
value: gender,
child: Text(gender),
))
.toList(),
onChanged: (val) {
setState(() {
_genderHasError = !(_formKey
.currentState?.fields['gender']
?.validate() ??
false);
});
},
valueTransformer: (val) => val?.toString(),
),
FormBuilderRadioGroup<String>(
decoration: const InputDecoration(
labelText: 'My chosen language',
),
initialValue: null,
name: 'best_language',
onChanged: _onChanged,
validator: FormBuilderValidators.compose(
[FormBuilderValidators.required()]),
options: ['Dart', 'Kotlin', 'Java', 'Swift', 'Objective-C']
.map((lang) => FormBuilderFieldOption(
value: lang,
child: Text(lang),
))
.toList(growable: false),
controlAffinity: ControlAffinity.trailing,
),
FormBuilderSwitch(
title: const Text('I Accept the terms and conditions'),
name: 'accept_terms_switch',
initialValue: true,
onChanged: _onChanged,
),
FormBuilderCheckboxGroup<String>(
autovalidateMode: AutovalidateMode.onUserInteraction,
decoration: const InputDecoration(
labelText: 'The language of my people'),
name: 'languages',
// initialValue: const ['Dart'],
options: const [
FormBuilderFieldOption(value: 'Dart'),
FormBuilderFieldOption(value: 'Kotlin'),
FormBuilderFieldOption(value: 'Java'),
FormBuilderFieldOption(value: 'Swift'),
FormBuilderFieldOption(value: 'Objective-C'),
],
onChanged: _onChanged,
separator: const VerticalDivider(
width: 10,
thickness: 5,
color: Colors.red,
),
validator: FormBuilderValidators.compose([
FormBuilderValidators.minLength(1),
FormBuilderValidators.maxLength(3),
]),
),
FormBuilderFilterChip<String>(
autovalidateMode: AutovalidateMode.onUserInteraction,
decoration: const InputDecoration(
labelText: 'The language of my people'),
name: 'languages_filter',
selectedColor: Colors.red,
options: const [
FormBuilderChipOption(
value: 'Dart',
avatar: CircleAvatar(child: Text('D')),
),
FormBuilderChipOption(
value: 'Kotlin',
avatar: CircleAvatar(child: Text('K')),
),
FormBuilderChipOption(
value: 'Java',
avatar: CircleAvatar(child: Text('J')),
),
FormBuilderChipOption(
value: 'Swift',
avatar: CircleAvatar(child: Text('S')),
),
FormBuilderChipOption(
value: 'Objective-C',
avatar: CircleAvatar(child: Text('O')),
),
],
onChanged: _onChanged,
validator: FormBuilderValidators.compose([
FormBuilderValidators.minLength(1),
FormBuilderValidators.maxLength(3),
]),
),
FormBuilderChoiceChip<String>(
autovalidateMode: AutovalidateMode.onUserInteraction,
decoration: const InputDecoration(
labelText:
'Ok, if I had to choose one language, it would be:'),
name: 'languages_choice',
initialValue: 'Dart',
options: const [
FormBuilderChipOption(
value: 'Dart',
avatar: CircleAvatar(child: Text('D')),
),
FormBuilderChipOption(
value: 'Kotlin',
avatar: CircleAvatar(child: Text('K')),
),
FormBuilderChipOption(
value: 'Java',
avatar: CircleAvatar(child: Text('J')),
),
FormBuilderChipOption(
value: 'Swift',
avatar: CircleAvatar(child: Text('S')),
),
FormBuilderChipOption(
value: 'Objective-C',
avatar: CircleAvatar(child: Text('O')),
),
],
onChanged: _onChanged,
),
],
),
),
Row(
children: <Widget>[
Expanded(
child: ElevatedButton(
onPressed: () {
if (_formKey.currentState?.saveAndValidate() ?? false) {
debugPrint(_formKey.currentState?.value.toString());
} else {
debugPrint(_formKey.currentState?.value.toString());
debugPrint('validation failed');
}
},
child: const Text(
'Submit',
style: TextStyle(color: Colors.white),
),
),
),
const SizedBox(width: 20),
Expanded(
child: OutlinedButton(
onPressed: () {
_formKey.currentState?.reset();
},
// color: Theme.of(context).colorScheme.secondary,
child: Text(
'Reset',
style: TextStyle(
color: Theme.of(context).colorScheme.secondary),
),
),
),
],
),
],
),
);
}
}
Current Behavior
If the form is reset all validation errors will be shown.
Expected Behavior
The form should reset and validation errors should be hidden.
Steps To Reproduce
Run the code, fill the form then tap to reset button.
Aditional information
The expected behavior works with default Form of Flutter.
ozkayas commented
neoacevedo commented
But ins't the idea?
bh-ous commented
I've faced this issue. After checking the code, the issue seems to be because of calling didChange
after calling super.reset
in flutter_form_builder-9.3.0/lib/src/form_builder_field.dart Line 210-211
. Calling super.reset
is going to set hasInteractedByUser
to false
which didChange
is going to set to true
later (line 211).
neoacevedo commented
But that's the idea, to mark all empty fields once the form has been cleared.