Open brunodmn opened 1 year ago
Hi @brunodmn,
The ReactiveFormArray by itself does not show any error since it is only a widget that helps you listen for changes in a FormArray.
The children on the other way should be able to display its own errors. The code you sent above is using a Checkbox, and coincidentally Checkboxes are some of the few Flutter controls that doesn't have error messages (you will have to add an extra widget to display messages). For the purpose of testing try to use a ReactiveTextField, it should display the error messages.
Let me know if there is anything else I can help with.
Thanks
Hi @joanpablo ,
Thank your for quick reply and for your package, I loved using reactive forms!
In my original project I am using ReactiveTextField still with no success, below I added snipeds of the validator and the form itself, I can move them to minimum reproducible project if necessary and send it here.
Form group:
fb.group({
'bannerUrls':
fb.array<String>([], [const IsUrlListValid()])
}
My Validator:
class IsUrlListValid extends Validator<dynamic> {
const IsUrlListValid() : super();
@override
Map<String, dynamic>? validate(AbstractControl<dynamic> control) {
final List<String?> strList = control.value ?? [];
final isAnyEmpty = strList.any((str) => (str ?? '').isEmpty);
if (isAnyEmpty) {
return {'required': true};
}
final isAnyInvalid = strList.any((str) => !(str ?? '').isURL);
if (isAnyInvalid) {
return {'invalidUrl': true};
}
return null;
}
}
My Widget:
ReactiveStatusListenableBuilder(
formControl: controller.form,
builder: (context, status, widget) {
final form = status as FormGroup;
return Text('${form.valid}');
},
),
ReactiveFormArray<String>(
key: UniqueKey(),
formArrayName: 'bannerUrls',
builder: (context, formArray, child) {
return Column(
children: [
...controller.bannerUrls
.map((url) => ReactiveTextField(
key: ValueKey(url),
formControlName: controller.bannerUrls
.indexOf(url)
.toString()
))
],
);
},
),
The logic seems to work as ReactiveStatusListenableBuilder returns the correct state while the field is being changed. I also tested similar validator for a field instead of a list and works properly showing all messages.
For instance, I use a pretty nested fb, on original code this array is named 'feature.marketing.bannerUrls'. I am now extracting the controls over nested forms and checking valid state, such as:
ReactiveStatusListenableBuilder(
formControl: controller.form,
builder: (context, status, widget) {
final form = status as FormGroup;
final marketing =
form.control('feature.marketing')
as FormGroup;
final aryControl =
marketing.control('bannerUrls')
as FormArray<String?>;
// print(aryControl.controls.length);
for (var control in aryControl.controls) {
final c = control as FormControl<String?>;
print(c.value.toString() +
' is valid? ${c.valid}');
}
return Text('${marketing.valid}');
},
),
On the upper code,aryControl.controls are always returning "is valid? true" even though the marketing.valid is false. I am not editing other fields on this marketing fb that can possibly change its state.
Below is code returns the expected result:
form.valueChanges.listen((event) {
final hasError = form.hasError("required", 'feature.marketing.bannerUrls');
print('$hasError');
});
Seems like the valid state is not being passed to children of the FormArray (nested ReactiveTextFields) which might not trigger the message.
Is there something I did wrong on the code?
@joanpablo , how can I access validation message, so I could use a workaround using ReactiveStatusListenableBuilder?
Something like bellow code, but using the message registered on ReactiveFormConfig.
ReactiveStatusListenableBuilder(
key: UniqueKey(),
formControl: controller.form
.control("feature.marketing.bannerUrls"),
builder: (context, state, widget) {
final f = state as FormArray<String?>;
if (f.errors.isNotEmpty) {
final error = f.errors.entries.first;
if (error.key == 'alreadyExists') {
return Row(
children: [
Text('This register already exists',
style: context
.textTheme.bodyMedium
?.copyWith(
color: context
.theme
.colorScheme
.error)),
],
);
}
}
return const SizedBox.shrink();
}),
Hi @brunodmn I will take a look at your code to understand the issue and let you know.
BTW, I would not use
key: ValueKey(url)
as key of the ReactiveTextField instead I would use something like Object key using the control as object. Just an observation.
BTW, I would not use
key: ValueKey(url)
as key of the ReactiveTextField instead I would use something like Object key using the control as object. Just an observation.
Thanks, It solved my problem while delete nested FormGroup
in FormArray
Trying to validate my array with custom validator, it works as form.valid state toggle. But validation message not shows near ReactiveFormArray nor its children. Thru documentation, was not clear if I had to do something additional to make this work.
I am using getx as state managment lib (this sample basic separates logic from ui)..code bellow
Controller
import 'package:get/get.dart'; import 'package:reactive_forms/reactive_forms.dart'; class Contact { final String email; final String name; Contact({required this.email, required this.name}); } class _EmptyAdresses extends Validator> getAllContacts() async { await 1.delay(); return [ Contact(email: "teste1gmail.com", name: "teste1"), Contact(email: "teste2gmail.com", name: "teste2") ]; } Future
> getMyContacts() async { await 1.delay(); return [ Contact(email: "teste1gmail.com", name: "teste1"), ]; } final contacts =[].obs;
_init() async {
_isLoading(true);
await 1.delay();
contacts.value = await getAllContacts();
final selectedContacts = await getMyContacts();
form.value = {
'name': 'John Doe',
'email': 'john@gmail.com',
'selectedContacts': contacts
.map((contact) =>
selectedContacts.indexWhere(
(selectedContact) => selectedContact.email == contact.email) >
-1)
.toList()
};
_isLoading(false);
}
save() {
contacts.length.printInfo();
form.value.printInfo();
}
remove(Contact contact) {
final selectedContacts =
form.control('selectedContacts') as FormArray;
final index = contacts.indexOf(contact);
contacts.removeAt(index);
selectedContacts.removeAt(index);
}
add() {
final selectedContacts =
form.control('selectedContacts') as FormArray;
contacts.add(Contact(
email: 'email${contacts.length + 1}@gmail.com',
name: 'Contact${contacts.length + 1}'));
selectedContacts.add(FormControl(value: true));
}
}
Page
import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:reactive_forms/reactive_forms.dart'; import 'controller.dart'; class HomePage extends GetViewMost important is below widget..is anything missing to this shows validation messages?