5

我在步进器的每个步骤中有多个表单,表单在外部文件中,因为在我的应用程序中,每个步骤都可以包含不同的表单。我希望当用户单击“继续”时,表单将得到验证,并且在错误情况下会警告用户。我尝试使用 Inherited Widget,但它给了我一个“getter 上的 null”。下面的代码:包含步骤的屏幕

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,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

第一步中包含的表格

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°")),
          ),
        ],
      ),
    );
  }
}

继承类,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);
}

我应该使用继承类管理验证还是有其他一些方法?

4

1 回答 1

26

使用列表 GlobalKey 来保留每个表单的键,并在 Continue 调用 formKeys[currStep].currentState.validate()
formKeys 是全局变量,对于单独的表单文件,您可以使用全局库访问它Dart 中的全局变量
对于演示,每个 from 只有一个字段

代码片段

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;
                }
              } 

完整代码

    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,
            ),
          ]),
        ));
  }
}

工作演示

在此处输入图像描述

于 2019-09-19T06:14:21.257 回答