delmin
delmin

Reputation: 2690

How to validate each form Step in Stepper

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

Answers (2)

LonelyWolf
LonelyWolf

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

delmin
delmin

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

Related Questions