Flutter GlobalKey 当前状态为空

Ami*_*ine 18 state flutter

我基本上想要做的(我做了,细节在前面)是创建一个屏幕,其中包含几个表单,其中的内容使用按钮(NEXT)进行更改。所以我有 3 个表单,当我按下下一步时,我保存第一个表单并传递给另一个表单,当我将我的类放在 main.dart 中时,我到目前为止所做的工作有效。例如,这是我的 main.dart 内容。

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Entries'),
    );
  }
}
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;
  static final _scafoldKey = new GlobalKey<ScaffoldState>();
  final firstForm = new FirstForm(title: "First Form");
  final secondForm = new SecondForm(title: "First Form",);
  final thirdForm = new ThirdForm(title: "First Form");
  final Map<int,Widget> forms = {};
  final Map<String,Map> allValues = new Map();

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  Widget currentForm;
  final Firestore _fireStore = Firestore.instance;
  Map<String,Map> thirdFormNested = new Map();
  int thirdFormPos = 1;

  @override
  void initState() {
    widget.forms[0] = widget.firstForm;
    widget.forms[1] = widget.secondForm;
    widget.forms[2] = widget.thirdForm;
    currentForm = widget.forms[0];
    super.initState();
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: MyHomePage._scafoldKey,
      appBar: AppBar(
        title: Text(widget.title),
        leading: IconButton(
          icon: Icon(Icons.arrow_back),
          onPressed: () => Navigator.of(context).pop(),
        ),
      ),
      body: SingleChildScrollView(
        child: currentForm,
      ),
      floatingActionButton: Stack(
        children: <Widget>[
          Align(
            alignment: Alignment(0.4, 1),
            child: SizedBox(
              width: 75,
              child: FloatingActionButton(
                shape: RoundedRectangleBorder(),
                child: Text("NEXT"),
                onPressed: () {
                  GlobalKey<FormState> key;
                  if(currentForm is FirstForm) {
                    key= widget.firstForm.formKey;
                    if(key.currentState.validate()){
                      key.currentState.save();
                      widget.allValues["First Form"] = widget.firstForm.values;
                      setState(() {
                        currentForm = widget.forms[1];
                      });
                    }
                  }
                  else if(currentForm is SecondForm) {
                    setState(() {
                      currentForm = widget.forms[2];
                    });
                  }
                  else if (currentForm is ThirdForm) {
                    key = widget.thirdForm.formKey;
                    if(key.currentState.validate()){
                      key.currentState.save();
                      var tmp = widget.thirdForm.values;
                      widget.thirdForm.values = <String,String>{};
                      thirdFormNested.addAll({"Bit $thirdFormPos":tmp});
                      key.currentState.reset();
                      widget.thirdForm.build(context);
                      thirdFormPos++;
                    }
                  }
                },
              ),
            ),
          ),
          Align(
            alignment: Alignment(1, 1),
            child: SizedBox(
              width: 75,
              child: FloatingActionButton(
                shape: RoundedRectangleBorder(),
                child: Text("SUBMIT"),
                onPressed: () async {
                  GlobalKey<FormState> key;
                  if(currentForm is FirstForm) {
                    key= widget.firstForm.formKey;
                    if(key.currentState.validate()) {
                      key.currentState.save();
                      await _fireStore.collection('form').document(Uuid().v1()).setData(widget.firstForm.values);
                    }
                  }else if(currentForm is SecondForm) {
                    await _fireStore.collection('form').document(Uuid().v1()).setData(widget.firstForm.values);
                  }else if(currentForm is ThirdForm) {
                    key= widget.thirdForm.formKey;
                    if(key.currentState.validate()){
                      key.currentState.save();
                      if(thirdFormNested.length == 0) {
                        var tmp = widget.thirdForm.values;
                        widget.thirdForm.values = <String,String>{};
                        thirdFormNested.addAll({"Bit 1":tmp});
                      }else {
                        var tmp = widget.thirdForm.values;
                        widget.thirdForm.values = <String,String>{};
                        thirdFormNested.addAll({"Bit $thirdFormPos":tmp});
                      }
                      widget.allValues["Third Form"] = thirdFormNested;
                      await _fireStore.collection('form').document(Uuid().v1()).setData(widget.allValues);
                    }
                  }
                },
              ),
            ),
          ),
        ],
      )
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

这是第一种形式(其他形式相同,唯一的区别是输入):

class FirstForm extends StatelessWidget {
  FirstForm({Key key, this.title}) : super(key: key);
  final String title;
  final GlobalKey<FormState> _formKey = new GlobalKey<FormState>();
  Map<String,String> _values = new Map();

  get formKey => _formKey;
  get values => _values;
  set values(v) => _values = v;

  @override
  Widget build(BuildContext context) {
    print("FORM KEY: ${_formKey.currentState}");
    _values['Spud Date'] = _formatDate(DateTime.now().toString());
    _values['TD Date'] = _formatDate(DateTime.now().toString());
    return Form(
      key: _formKey,
      child: Column(
        children: <Widget>[
          ListTile(
            leading: Text(
              "API#",
              style: TextStyle(
                fontSize: 18,
              ),
            ),
            title: TextFormField(
              decoration: InputDecoration(
                hintText: "<api>"
              ),
              validator: (value) {
                if(value.isEmpty) {
                  return "Please enter API value!";
                }
                return null;
              },
              onSaved: (value) {
                _values['API#'] = value;
              },
            ),
          ..... Other inputs
          Padding(
            padding: EdgeInsets.symmetric(vertical: 36.0),
          )
        ],
      ),
    );
  }

  String _formatDate(String date) {
    DateTime d = DateTime.parse(date);
    return DateFormat("dd-MM-yyyy").format(d);
  }
}
Run Code Online (Sandbox Code Playgroud)

所以每个表单都有一个我传递给 Form(key: ..) 的 formkey。我实例化 MyHomePage 类中的每个表单,并使用它widget.firstForm.formKey来检索表单密钥以将其用于验证。

到目前为止,这一直在完美运行。但是,当我尝试将我的工作融入现有应用程序时。没有了。

在现有的应用程序中,我有一个抽屉。抽屉里的一个项目叫做 Forms,它把我带到“MyHomePage”,我现在把它重命名为 Forms。所以代码仍然相同,我唯一删除的是 runApp() 和 MyApp 类,它们现在位于不同的文件中。这是抽屉项目代码:

ListTile(
                title: new Text('Forms'),
                onTap: () {
                  Navigator.push(
                    context,
                    MaterialPageRoute(
                      maintainState: true,
                      builder: (context) => Forms(title: "Forms")
                    ),
                  );
                },
              ),
Run Code Online (Sandbox Code Playgroud)

现在,使用这种方法。在“表单”屏幕中单击“下一步”时,出现错误:

validate() 在 null 上调用

所以几个小时后,我发现实际上是我的表单的 GlobalKey 返回了一个空状态。问题是为什么?我该如何解决这个问题。

TL; DR:我有 3 个表格,它们分别带有 GlobalKeys。密钥用于验证另一个小部件中的表单,该小部件包装这些表单。当我在 main.dart 中的默认 MyHomePage 类中有包装小部件时,我接近他们的创作方式。但是,当将相同的包装类移动到外部文件并将类重命名为更合适的名称时,GlobalKeys 状态现在为空。

编辑:我注意到的一个额外尴尬的行为是,当我填写表单 (FirstForm) 并在运行应用程序时第一次按 Next 时,出现空错误。但是,在应用程序的第一次运行时,如果我在没有提交表单的情况下按下一步,验证器就会工作,它会向我显示填写输入的错误。

JJ_*_*ire 7

您需要将密钥传递到脚手架小部件中,然后将设置上下文和状态。

class _MyHomePageState extends State<MyHomePage> {
  GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey(); //so we can call snackbar from anywhere
Run Code Online (Sandbox Code Playgroud)

...

Widget build(BuildContext context) {
    return Scaffold(
        key: _scaffoldKey, //set state and context
Run Code Online (Sandbox Code Playgroud)

...

//define method to call
void showInSnackBar(String value) {
    _scaffoldKey.currentState.showSnackBar(new SnackBar(
        content: new Text(value)
    ));
  }
Run Code Online (Sandbox Code Playgroud)

...

//Then you can call it
showInSnackBar("Proximity Alert, please distance yourself from the person or dangerous object close to you! This incident has been logged.");
Run Code Online (Sandbox Code Playgroud)


Ami*_*ine 3

如果有人有同样的问题。我还没有找到解决方案,或者至少没有找到为什么会发生这种情况的解释。但是,我从使用自己的方法改为使用 Inherited Widget 来传输表单数据,同时将验证保留在每个单独的表单内(我在包装类 Forms 内进行验证,现在我在其自己的类中验证每个表单)。