Reputation: 2690
I'm trying to validate form in each step of stepper. I'm creating the list for stepper from map so. Problem is that if I add Form for each step I get error
Multiple widgets used the same GlobalKey.
How do I create dynamically new key for each Form
List<Step> stepList() {
List<Step> _steps = [];
list.forEach((k, v) => _steps.add(Step(
title: Text(k),
content: Column(
children: v.map<Widget>((child) {
return Form(key: _formKey, child: child);
}).toList(),
),
)));
return _steps;
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stepper(
onStepContinue: next,
currentStep: current,
onStepCancel: cancel,
steps: stepList(),
),
);
}
Here is complete copy-paste code... There is also attempt to create dynamically new key
class _MaterialScreenState extends State<MaterialScreen> {
int current = 0;
bool complete = false;
//final _formKey = GlobalKey<FormState>();
List<GlobalKey<FormState>> _getFormKeys() { //attempt to create list of Form keys for each step
List<GlobalKey<FormState>> l = [];
keyList().forEach((v) => l.add(GlobalKey<FormState>()));
return l;
}
goTo(int step) {
setState(() => current = step);
}
bool _validate() { // this method is called on null
final form = _getFormKeys()[current].currentState;
if (form.validate()) {
return true;
}
return false;
}
next() {
bool validated = _validate();
print(validated);
if (complete == false) {
if (current + 1 != list.length && validated) {
goTo(current + 1);
}
if (current + 1 == list.length) {
setState(() {
complete = true;
});
}
} else {
print('saved');
}
}
cancel() {
if (current > 0) {
goTo(current - 1);
}
}
List<String> keyList() {
List<String> l = [];
list.forEach((k, v) {
l.add(k);
});
return l;
}
Map<String, List<Widget>> list = {
'Step1': [
TextFormField(validator: (s) => s.isNotEmpty ? null : 'Required')
],
'Step2': [
TextFormField(validator: (s) => s.isNotEmpty ? null : 'Required')
],
'Step3': [TextFormField(validator: (s) => s.isNotEmpty ? null : 'Required')]
};
List<Step> stepList() {
List<Step> _steps = [];
list.forEach((k, v) => _steps.add(Step(
title: Text(k),
content: Column(
children: v.map<Widget>((child) {
return Form(
key: _getFormKeys()[keyList().indexOf(k)], child: child);
}).toList(),
),
)));
return _steps;
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stepper(
onStepContinue: next,
currentStep: current,
onStepCancel: cancel,
steps: stepList(),
),
);
}
}
Upvotes: 1
Views: 1892
Reputation: 4392
The reason why it didn't work and your validate method was called on null is because you have created brad new GlobalKey in your method _getFormKeys() each time you called it. All you had to do is to initialise your list of globalKey in initState and then used them as you were doing it
class _MaterialScreenState extends State<MaterialScreen> {
int current = 0;
bool complete = false;
List<GlobalKey<FormState>> _formKeyList = []; //<- create variable for the list
Map<String, List<Widget>> list = {
'Step1': [
TextFormField(validator: (s) => s.isNotEmpty ? null : 'Required')
],
'Step2': [
TextFormField(validator: (s) => s.isNotEmpty ? null : 'Required')
],
'Step3': [TextFormField(validator: (s) => s.isNotEmpty ? null : 'Required')]
};
void initState() {
list.forEach((k, v) {
_formKeyList.add(GlobalKey<FormState>()); //<-- initialise it here
});
super.initState();
}
goTo(int step) {
setState(() => current = step);
}
bool _validate() {
// this method is called on null
final form = _formKeyList[current].currentState;
if (form.validate()) {
return true;
}
return false;
}
next() {
bool validated = _validate();
print(validated);
if (complete == false) {
if (current + 1 != list.length && validated) {
goTo(current + 1);
}
if (current + 1 == list.length) {
setState(() {
complete = true;
});
}
} else {
print('saved');
}
}
cancel() {
if (current > 0) {
goTo(current - 1);
}
}
List<String> keyList() {
List<String> l = [];
list.forEach((k, v) {
l.add(k);
});
return l;
}
List<Step> stepList() {
List<Step> _steps = [];
list.forEach((k, v) => _steps.add(Step(
title: Text(k),
content: Column(
children: v.map<Widget>((child) {
return Form(
key: _formKeyList[keyList().indexOf(k)], child: child);
}).toList(),
),
)));
return _steps;
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stepper(
onStepContinue: next,
currentStep: current,
onStepCancel: cancel,
steps: stepList(),
),
);
}
}
Upvotes: 1
Reputation: 2690
Thanks to @pskink the question is answered
Here is complete code how to validate each form step in stepper
class MaterialScreen extends StatefulWidget {
@override
_MaterialScreenState createState() => _MaterialScreenState();
}
class _MaterialScreenState extends State<MaterialScreen> {
Map<String, List<Widget>> list = {
'Step1': [TextFormField(validator: (s) => s.isNotEmpty ? null : 'Required')],
'Step2': [TextFormField(validator: (s) => s.isNotEmpty ? null : 'Required')],
'Step3': [TextFormField(validator: (s) => s.isNotEmpty ? null : 'Required')],
};
Map steps;
@override
void initState() {
super.initState();
steps = list.map<GlobalKey, Step>(_makeStep);
}
MapEntry<GlobalKey, Step> _makeStep(k, v) {
var key = GlobalKey();
var step = Step(
title: Text(k),
content: Form(key: key, child: Column(children: v,),),
);
return MapEntry(key, step);
}
int current = 0;
bool complete = false;
goTo(int step) {
setState(() => current = step);
}
bool _validate() {
final form = steps.keys.elementAt(current).currentState;
return form.validate();
}
next() {
bool validated = _validate();
print(validated);
if (complete == false) {
if (current + 1 != list.length && validated) {
goTo(current + 1);
}
if (current + 1 == list.length) {
setState(() {
complete = true;
});
}
} else {
print('saved');
}
}
cancel() {
if (current > 0) {
goTo(current - 1);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stepper(
onStepContinue: next,
currentStep: current,
onStepCancel: cancel,
steps: steps.values.toList(),
),
);
}
}
Upvotes: 1