Manage multiple form (validation) in stepper flutter

11,704

Use a list GlobalKey to keep each form's key and in Continue call formKeys[currStep].currentState.validate()
formKeys is global variable, In your case for separate form file you can use global library to access it Global Variables in Dart
For demo, each from only have one field

code snippet

List<GlobalKey<FormState>> formKeys = [GlobalKey<FormState>(), GlobalKey<FormState>(), GlobalKey<FormState>(), GlobalKey<FormState>()];
...
onStepContinue: () {             
            setState(() {
              if(formKeys[currStep].currentState.validate()) {
                if (currStep < steps.length - 1) {
                  currStep = currStep + 1;
                } else {
                  currStep = 0;
                }
              } 

full code

    import 'package:flutter/material.dart';
//import 'package:validate/validate.dart';  //for validation

void main() {
  runApp( MyApp());
}

List<GlobalKey<FormState>> formKeys = [GlobalKey<FormState>(), GlobalKey<FormState>(), GlobalKey<FormState>(), GlobalKey<FormState>()];

class MyApp extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return  MyAppScreenMode();
  }
}

class MyData {
  String name = '';
  String phone = '';
  String email = '';
  String age = '';
}

class MyAppScreenMode extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return  MaterialApp(
        theme:  ThemeData(
          primarySwatch: Colors.lightGreen,
        ),
        home:  Scaffold(
          appBar:  AppBar(
            title:  Text('Steppers'),
          ),
          body:  StepperBody(),
        ));
  }
}

class StepperBody extends StatefulWidget {
  @override
  _StepperBodyState createState() =>  _StepperBodyState();
}

class _StepperBodyState extends State<StepperBody> {
  int currStep = 0;
  static var _focusNode =  FocusNode();
  GlobalKey<FormState> _formKey =  GlobalKey<FormState>();
  static MyData data =  MyData();

  @override
  void initState() {
    super.initState();
    _focusNode.addListener(() {
      setState(() {});
      print('Has focus: $_focusNode.hasFocus');
    });
  }

  @override
  void dispose() {
    _focusNode.dispose();
    super.dispose();
  }



  List<Step> steps = [
     Step(
        title: const Text('Name'),
        //subtitle: const Text('Enter your name'),
        isActive: true,
        //state: StepState.error,
        state: StepState.indexed,
        content: Form(
          key: formKeys[0],
          child: Column(
            children: <Widget>[
               TextFormField(
                focusNode: _focusNode,
                keyboardType: TextInputType.text,
                autocorrect: false,
                onSaved: (String value) {
                  data.name = value;
                },
                maxLines: 1,
                //initialValue: 'Aseem Wangoo',
                validator: (value) {
                  if (value.isEmpty || value.length < 1) {
                    return 'Please enter name';
                  }
                },
                decoration:  InputDecoration(
                    labelText: 'Enter your name',
                    hintText: 'Enter a name',
                    //filled: true,
                    icon: const Icon(Icons.person),
                    labelStyle:
                     TextStyle(decorationStyle: TextDecorationStyle.solid)),
              ),
            ],
          ),
        )),
     Step(
        title: const Text('Phone'),
        //subtitle: const Text('Subtitle'),
        isActive: true,
        //state: StepState.editing,
        state: StepState.indexed,
        content: Form(
          key: formKeys[1],
          child: Column(
            children: <Widget>[
               TextFormField(
                keyboardType: TextInputType.phone,
                autocorrect: false,
                validator: (value) {
                  if (value.isEmpty || value.length < 10) {
                    return 'Please enter valid number';
                  }
                },
                onSaved: (String value) {
                  data.phone = value;
                },
                maxLines: 1,
                decoration:  InputDecoration(
                    labelText: 'Enter your number',
                    hintText: 'Enter a number',
                    icon: const Icon(Icons.phone),
                    labelStyle:
                     TextStyle(decorationStyle: TextDecorationStyle.solid)),
              ),
            ],
          ),
        )),
     Step(
        title: const Text('Email'),
        // subtitle: const Text('Subtitle'),
        isActive: true,
        state: StepState.indexed,
        // state: StepState.disabled,
        content:  Form(
          key: formKeys[2],
          child: Column(
            children: <Widget>[
              TextFormField(
                keyboardType: TextInputType.emailAddress,
                autocorrect: false,
                validator: (value) {
                  if (value.isEmpty || !value.contains('@')) {
                    return 'Please enter valid email';
                  }
                },
                onSaved: (String value) {
                  data.email = value;
                },
                maxLines: 1,
                decoration:  InputDecoration(
                    labelText: 'Enter your email',
                    hintText: 'Enter a email address',
                    icon: const Icon(Icons.email),
                    labelStyle:
                     TextStyle(decorationStyle: TextDecorationStyle.solid)),
              ),
            ],
          ),
        )),
     Step(
        title: const Text('Age'),
        // subtitle: const Text('Subtitle'),
        isActive: true,
        state: StepState.indexed,
        content:  Form(
          key: formKeys[3],
          child: Column(
            children: <Widget>[
              TextFormField(
                keyboardType: TextInputType.number,
                autocorrect: false,
                validator: (value) {
                  if (value.isEmpty || value.length > 2) {
                    return 'Please enter valid age';
                  }
                },
                maxLines: 1,
                onSaved: (String value) {
                  data.age = value;
                },
                decoration:  InputDecoration(
                    labelText: 'Enter your age',
                    hintText: 'Enter age',
                    icon: const Icon(Icons.explicit),
                    labelStyle:
                     TextStyle(decorationStyle: TextDecorationStyle.solid)),
              ),
            ],
          ),
        )),
    //  Step(
    //     title: const Text('Fifth Step'),
    //     subtitle: const Text('Subtitle'),
    //     isActive: true,
    //     state: StepState.complete,
    //     content: const Text('Enjoy Step Fifth'))
  ];

  @override
  Widget build(BuildContext context) {
    void showSnackBarMessage(String message,
        [MaterialColor color = Colors.red]) {
      Scaffold
          .of(context)
          .showSnackBar( SnackBar(content:  Text(message)));
    }

    void _submitDetails() {
      final FormState formState = _formKey.currentState;

      if (!formState.validate()) {
        showSnackBarMessage('Please enter correct data');
      } else {
        formState.save();
        print("Name: ${data.name}");
        print("Phone: ${data.phone}");
        print("Email: ${data.email}");
        print("Age: ${data.age}");

        showDialog(
            context: context,
            child:  AlertDialog(
              title:  Text("Details"),
              //content:  Text("Hello World"),
              content:  SingleChildScrollView(
                child:  ListBody(
                  children: <Widget>[
                     Text("Name : " + data.name),
                     Text("Phone : " + data.phone),
                     Text("Email : " + data.email),
                     Text("Age : " + data.age),
                  ],
                ),
              ),
              actions: <Widget>[
                 FlatButton(
                  child:  Text('OK'),
                  onPressed: () {
                    Navigator.of(context).pop();
                  },
                ),
              ],
            ));
      }
    }

    return  Container(
        child:  Form(
          key: _formKey,
          child:  ListView(children: <Widget>[
             Stepper(
              steps: steps,
              type: StepperType.vertical,
              currentStep: this.currStep,
              onStepContinue: () {             
                setState(() {
                  if(formKeys[currStep].currentState.validate()) {
                    if (currStep < steps.length - 1) {
                      currStep = currStep + 1;
                    } else {
                      currStep = 0;
                    }
                  }
                  // else {
                  // Scaffold
                  //     .of(context)
                  //     .showSnackBar( SnackBar(content:  Text('$currStep')));

                  // if (currStep == 1) {
                  //   print('First Step');
                  //   print('object' + FocusScope.of(context).toStringDeep());
                  // }

                  // }
                });
              },
              onStepCancel: () {
                setState(() {
                  if (currStep > 0) {
                    currStep = currStep - 1;
                  } else {
                    currStep = 0;
                  }
                });
              },
              onStepTapped: (step) {
                setState(() {
                  currStep = step;
                });
              },
            ),
             RaisedButton(
              child:  Text(
                'Save details',
                style:  TextStyle(color: Colors.white),
              ),
              onPressed: _submitDetails,
              color: Colors.blue,
            ),
          ]),
        ));
  }
}

working demo

enter image description here

Share:
11,704

Related videos on Youtube

Jerry Palmiotto
Author by

Jerry Palmiotto

Updated on June 04, 2022

Comments

  • Jerry Palmiotto
    Jerry Palmiotto almost 2 years

    I have multiple form in each step of a Stepper, Form are in external file because in my App each Step can contain a different Form. I want that when user click on "Continue" the form will be validated and in an error situation the user will be warned. I tried to use Inherited Widget but it gives me a "null on getter". The code below: Screen that contain Steps

    import 'package:flutter/material.dart';
    import 'package:pberrycoffeemaker/widgets/function_appbar.dart';
    import 'package:pberrycoffeemaker/widgets/inputs_0.dart';
    import 'package:pberrycoffeemaker/widgets/stepper_banner.dart';
    
    class FunctionScreen extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        int indexStep = 0;
        double bottomHeight = (MediaQuery.of(context).size.height * 40) / 100;
        double cardHeight = (MediaQuery.of(context).size.height * 60) / 100;
        double cardWidth = (MediaQuery.of(context).size.width * 85) / 100;
        FirstTypeInput firstTypeOfInput = FirstTypeInput();
        GlobalKey<FormState> key = new GlobalKey<FormState>();
        return Scaffold(
          body: Stack(
            children: <Widget>[
              AppbarBack(
                height: bottomHeight,
              ),
              Align(
                alignment: Alignment(0.0, .65),
                child: ClipRRect(
                  borderRadius: BorderRadius.all(Radius.circular(15.0)),
                  child: Container(
                    child: StepperBanner(
                      firstTypeInputKey: key,
                      test: 18,
                      firstTypeInputField: {},
                      child: Stepper(
                        currentStep: indexStep,
                        onStepContinue: (){
                          print(StepperBanner.of(context).test);
                          //StepperBanner.of(context).firstTypeInputKey.currentState.validate();
                        },
                        //type: StepperType.horizontal,
                        steps: <Step>[
                          Step(
                            content: firstTypeOfInput,
                            title: Text("Theorical"),
                          ),
                          Step(
                              content: //Second Step Content,
                              title: Text("Practical")),
                        ],
                      ),
                    ),
                    decoration: BoxDecoration(color: Colors.white, boxShadow: [
                      BoxShadow(
                          color: Colors.black,
                          blurRadius: 10.0,
                          offset: Offset(0.0, 0.75))
                    ]),
                    width: cardWidth,
                    height: cardHeight,
                  ),
                ),
              ),
            ],
          ),
        );
      }
    }
    
    

    Form contained in first Step

    import 'package:flutter/cupertino.dart';
    import 'package:flutter/material.dart';
    import 'package:flutter/services.dart';
    import 'package:pberrycoffeemaker/functions/coffeeCalculation.dart';
    import 'package:pberrycoffeemaker/widgets/stepper_banner.dart';
    
    // ignore: must_be_immutable
    class FirstTypeInput extends StatefulWidget {
    
      final Map<String, double> submittedField = {
        "tds": 0.0,
        "ext": 0.0,
        "dw": 0.0,
        "af": 0.0,
        "co2": 0.0,
        "co2p": 0.0,
        "ih": 0.0,
        "ihp": 0.0,
        "wtemp": 0.0,
        "twh": 0.0,
        "alk": 0.0
      };
    
      @override
      State<StatefulWidget> createState() {
        return _FirstTypeInput();
      }
    }
    
    class _FirstTypeInput extends State<FirstTypeInput> {
      Map<String, FocusNode> _focusNodes;
      GlobalKey<FormState> formKey;
      @override
      void initState() {
        super.initState();
        _focusNodes = {
          "ext": new FocusNode(),
          "dw": new FocusNode(),
          "af": new FocusNode(),
          "co2": new FocusNode(),
          "co2P": new FocusNode(),
          "ih": new FocusNode(),
          "ihP": new FocusNode(),
          "wt": new FocusNode(),
          "twh": new FocusNode(),
          "alk": new FocusNode()
        };
      }
    
      String percentageValidator(String value){
        if (double.parse(value) < 0 || double.parse(value) > 100){
          return "Insert value between 0 - 100";
        }
        return null;
      }
    
      @override
      Widget build(BuildContext context) {
        return Form(
          key: StepperBanner.of(context).firstTypeInputKey,
          //key: widget.formKey,
          child: Column(
            children: <Widget>[
              // * TDS
              TextFormField(
                //validator: percentageValidator,
                onFieldSubmitted: (value) {
                  FocusScope.of(context).requestFocus(_focusNodes["ext"]);
                },
                decoration: InputDecoration(
                    labelText: "TDS%", hintText: "Insert TDS%", suffix: Text("%")),
              ),
              // * EXT
              TextFormField(
                onFieldSubmitted: (value) {
                  FocusScope.of(context).requestFocus(_focusNodes["dw"]);
                },
                focusNode: _focusNodes["ext"],
                decoration: InputDecoration(
                    labelText: "EXT%", hintText: "Insert EXT%", suffix: Text("%")),
              ),
              // * Drink Weight
              TextFormField(
                onFieldSubmitted: (value) {
                  FocusScope.of(context).requestFocus(_focusNodes["af"]);
                },
                focusNode: _focusNodes["dw"],
                decoration: InputDecoration(
                    labelText: "Drink Weight",
                    hintText: "Insert drink weight",
                    suffix: Text("g")),
              ),
              // * Absorption Factor
              TextFormField(
                onFieldSubmitted: (value) {
                  FocusScope.of(context).requestFocus(_focusNodes["co2"]);
                },
                focusNode: _focusNodes["af"],
                decoration: InputDecoration(
                    labelText: "Absorption Factor",
                    hintText: "Insert absorptio factor",
                    suffix: Text("g")),
              ),
              // * CO2
              TextFormField(
                onFieldSubmitted: (value) {
                  FocusScope.of(context).requestFocus(_focusNodes["co2P"]);
                },
                focusNode: _focusNodes["co2"],
                decoration: InputDecoration(
                    labelText: "CO2", hintText: "Insert CO2", suffix: Text("g")),
              ),
              // * CO2 Precision
              TextFormField(
                onFieldSubmitted: (value) {
                  FocusScope.of(context).requestFocus(_focusNodes["ih"]);
                },
                focusNode: _focusNodes["co2P"],
                decoration: InputDecoration(
                    labelText: "CO2 Precision",
                    hintText: "Insert CO2 Precision",
                    suffix: Text("%")),
              ),
              // * Internal Humidity
              TextFormField(
                onFieldSubmitted: (value) {
                  FocusScope.of(context).requestFocus(_focusNodes["ihP"]);
                },
                focusNode: _focusNodes["ih"],
                decoration: InputDecoration(
                    labelText: "Internal Humidity",
                    hintText: "Insert internal humidity",
                    suffix: Text("%")),
              ),
              // * Internal Humidity Precision
              TextFormField(
                onFieldSubmitted: (value) {
                  FocusScope.of(context).requestFocus(_focusNodes["wt"]);
                },
                focusNode: _focusNodes["ihP"],
                decoration: InputDecoration(
                    labelText: "Internal Humidity Precision",
                    hintText: "Insert internal humidity precision",
                    suffix: Text("%")),
              ),
              // * Water Temperature
              TextFormField(
                onFieldSubmitted: (value) {
                  FocusScope.of(context).requestFocus(_focusNodes["twh"]);
                },
                focusNode: _focusNodes["wt"],
                decoration: InputDecoration(
                    labelText: "Water Temperature",
                    hintText: "Insert water temperature",
                    //TODO da decidere se settare nelle impostazioni l'unità di misura oppure mettere un dropdown sotto
                    suffix: Text("C°|F°|K°")),
              ),
              // * Total Water Hardness
              TextFormField(
                onFieldSubmitted: (value) {
                  FocusScope.of(context).requestFocus(_focusNodes["alk"]);
                },
                focusNode: _focusNodes["twh"],
                decoration: InputDecoration(
                    labelText: "Total Water Hardness",
                    hintText: "Insert total water hardness",
                    //TODO da decidere se settare nelle impostazioni l'unità di misura oppure mettere un dropdown sotto
                    suffix: Text("PPM°|F°|D°")),
              ),
              // * Alkalinity
              TextFormField(
                focusNode: _focusNodes["alk"],
                decoration: InputDecoration(
                    labelText: "Alkalinity",
                    hintText: "Insert alkalinity",
                    //TODO da decidere se settare nelle impostazioni l'unità di misura oppure mettere un dropdown sotto
                    suffix: Text("PPM°|F°|D°")),
              ),
            ],
          ),
        );
      }
    }
    

    Inherited Class, StepperBanner:

    import 'package:flutter/material.dart';
    
    class StepperBanner extends InheritedWidget {
      final Map<String, double> firstTypeInputField;
      final GlobalKey<FormState> firstTypeInputKey;
      final int test;
    
      StepperBanner({Widget child, this.firstTypeInputField,this.test, this.firstTypeInputKey}) : super(child: child);
    
      @override
      bool updateShouldNotify(InheritedWidget oldWidget) => true;
    
      static StepperBanner of(BuildContext context) =>
          context.inheritFromWidgetOfExactType(StepperBanner);
    }
    
    

    Should I manage validation with Inherited Class or there are some other metods?

  • StefanSpeterDev
    StefanSpeterDev about 4 years
    Hey thank you for sharing it works great and it's easy to modify up to what we need, I've searched for hours before finding on your answer!
  • Dani
    Dani almost 3 years
    that's fine for when we want to validate step by step but what about if we need to validate the whole thing?
  • rosh-dev851
    rosh-dev851 over 2 years
    this.formKeys.every((element) => element.currentState!.validate());