Open rsbichkar opened 1 year ago
Hi @rsbichkar,
Have you tried to use $i.degree
as the formControlName in children widgets of the ReactiveFormArray?
Thanks for a very quick reply. It worked. I am sure this approach will work if for higher levels of nesting.
I'm glad that you have solved the issue. Thanks to you for the issue.
I will close it now, but if you have any other questions don't hesitate to create a new one, or reopen this one.
Thank you very much. I tried to embed a FormArray in another class i.e. Person<-College<-Qualif, where Qualif has a FormArray. It also worked fine.
You're most welcome @rsbichkar
Hello Joan,
In continuation with the earlier problem, I am now trying to render a FormArray having FormGroups. I wish to display the add, delete, move_up, and move_down buttons on individual array elements. The entire code is given below. It has following parts:
The problem is that the add (delete) button adds (deletes) an element at the end of the array instead of the current element. Also the move_up and move_down buttons do not work at all.
I felt that this has something to do with the array state and used ReactiveFormBuilder instead of ReactiveForm. But it did not help. What is the solution?
import 'package:flutter/material.dart';
import 'package:reactive_forms/reactive_forms.dart';
class Qualif {
final String degree;
final int year;
const Qualif(this.degree, this.year, {Key? key});
}
FormGroup qualifFormGroup() {
return FormGroup({
'degree': FormControl<String>(),
'year': FormControl<int>(),
});
}
List<Widget> qualifFormFields(int i) {
return [
ReactiveTextField(
formControlName: '$i.degree',
// formControl: control as FormControl<String>,
decoration: const InputDecoration(
label: Text('Degree'),
),
),
ReactiveTextField(
formControlName: '$i.year',
// formControl: control as FormControl<String>,
decoration: const InputDecoration(
label: Text('Year'),
),
),
];
}
class Person {
final String name;
final int age;
final List<Qualif> qualif;
const Person(this.name, this.age, this.qualif, {Key? key});
}
FormGroup personFormGroup() {
return FormGroup({
'name': FormControl<String>(),
'age': FormControl<int>(),
'college': FormGroup({
'name': FormControl<String>(),
'qualif': FormArray([
qualifFormGroup(),
]),
})
});
}
List<Widget> personFormFields() {
return [
ReactiveTextField(
formControlName: 'name',
decoration: const InputDecoration(
label: Text('Name'),
),
),
ReactiveTextField(
formControlName: 'age',
decoration: const InputDecoration(
label: Text('Age'),
),
),
const ReactiveArrayFormFields(qualifFormFields, 'Qualifications'),
];
}
class PersonFormView extends StatelessWidget {
final FormGroup form;
const PersonFormView(this.form, {super.key});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: SingleChildScrollView(
// child: ReactiveForm(
// formGroup: form,
child: ReactiveFormBuilder(
form: () => form,
builder: (context, form, child) {
return Column(
children: personFormFields(),
);
},
),
),
);
}
}
class ReactiveArrayFormFields<T> extends StatelessWidget {
final Function arrayFields;
final String label;
const ReactiveArrayFormFields(
this.arrayFields,
this.label, {
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
// --------- using formControlName ---------
return ReactiveFormArray(
formArrayName: 'college.qualif',
builder: (context, array, child) => Column(
children: [
// for (final control in array.controls)
for (int i = 0; i < array.controls.length; i++)
Column(
children: [
Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Expanded(
child: Container(
color: Colors.grey[200],
child: Column(children: [
...arrayFields(i),
const SizedBox(height: 10),
]),
),
),
// const SizedBox(width: 8),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
// crossAxisAlignment: CrossAxisAlignment.center,
children: [
CircularButton(
i == 0 ? null : () => array.insert(i - 1, array.removeAt(i)), Icons.move_up, Colors.purple, Colors.white),
CircularButton(
array.controls.length > 1 ? () => array.removeAt(i) : null, Icons.remove, Colors.red, Colors.white),
CircularButton(() => array.add(qualifFormGroup()), Icons.add, Colors.green, Colors.white),
CircularButton(i == array.controls.length - 1 ? null : () => array.insert(i, array.removeAt(i)), Icons.move_down,
Colors.purple, Colors.white),
],
),
),
],
),
const SizedBox(height: 12),
],
),
],
),
);
}
}
class CircularButton extends StatelessWidget {
const CircularButton(this.onPressed, this.icon, this.bgColor, this.fgColor, {Key? key}) : super(key: key);
final VoidCallback? onPressed;
final IconData icon;
final Color bgColor;
final Color fgColor;
// final Key? key;
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onPressed,
style: ElevatedButton.styleFrom(
shape: const CircleBorder(),
padding: const EdgeInsets.all(8),
backgroundColor: bgColor, // <-- Button color
foregroundColor: fgColor, // <-- Splash color
),
child: Icon(icon, color: Colors.white),
);
}
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'FormArray Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: PersonFormView(personFormGroup()));
}
}
void main() {
runApp(const MyApp());
}
I am sorry. My bad. There is an error in the PersonFormGroup() function which returns a FormGroup for Person. The erroneous code is given below where the 'college' field remained from the previous implementation.
class Person {
final String name;
final int age;
final List<Qualif> qualif;
const Person(this.name, this.age, this.qualif, {Key? key});
}
FormGroup personFormGroup() {
return FormGroup({
'name': FormControl<String>(),
'age': FormControl<int>(),
'college': FormGroup({
'name': FormControl<String>(),
'qualif': FormArray([
qualifFormGroup(),
]),
})
});
}
It should have been written as
FormGroup personFormGroup() {
return FormGroup({
'name': FormControl<String>(),
'age': FormControl<int>(),
'qualif': FormArray([
qualifFormGroup(),
]),
});
}
I have made the correction and the code is working fine. A slight modification is also required in callbacks for Add/Insert (+) and move_down buttons as well.
I will test the code further and report problems if any.
Thanks for your support and sorry once again for raising this issue. If possible, we can delete last submission. I will close this issue shortly. Shall I provide the final working code so that it can help someone else using this feature? May be it can be added as an Example to the library.
Hi @rsbichkar,
Feel free to post the final code. I would advice you to use Keys in all you children widgets of the ReactiveFormArray. All the widgets that you add/remove should have a Key. You could use an ObjectKey(with the control itself as the object), or ValueKey(with a string as the name and/or index position of the control as value)
I am trying to implement a form having a FormArray of FormGroups. However, I am unable to get it working. I could not find a working implementation for the same, in the package examples or the Internet. The problem is with writing Widgets for the FormArray part. I have tried using several approaches, but no success.
Here is the sample code that uses two approaches using formControl and formControlName in a ReactiveTextField. While using formControl, the casting gives the error in
Whereas, in formControlName approach, specifying the name as in
qualif.$i.degree
gives a problem (where $i refers to index as suggested in the array example).Please suggest where I am making mistakes or provide a link to a working example.
The code is given here.