Flutter:当字段在ListView中不可见时,TextFormField验证器未被调用

7
我遇到了与这个问题相同的问题,并通过遵循提供的建议来解决(我在onChanged中调用setState())...问题是,在我的情况下,如果字段在视图中不可见(如果您滚动视图直到其不再显示),则validator函数不会被调用,因此我可以以无效状态提交表单。

我该如何触发当前未显示在ListView中的字段的验证?

更新:

以下是一个简单的演示问题的示例(我正在使用它来在隔离的简单应用程序中进行调试):

import 'package:flutter/material.dart';

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

class MyForm extends StatefulWidget {
  @override
  State createState() {
    return _MyFormState();
  }
}

class _MyFormState extends State<MyForm> {
  static final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  final Map<int, String> model = Map<int, String>();

  @override
  void initState() {
    print('initState()');
    for (int i = 0; i < 20; i++) {
      model[i] = null;
    }
    super.initState();
  }

  String validate(String value) {
    return value.isEmpty ? 'Cannot be blank!' : null;
  }

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

    if (formState.validate()) {
      print('form is valid');
      formState.save();
    } else {
      print('form is invalid');
    }
  }

  @override
  Widget build(BuildContext context) {
    List<Widget> fields = [];

    for (int i = 0; i < 20; i++) {
      Widget widget = TextFormField(
        initialValue: model[i],
        decoration: InputDecoration(
          labelText: 'field $i',
        ),
        validator: validate,
        onFieldSubmitted: (String value) {
          print('field $i onFieldSubmitted()');
          setState(() {
            model[i] = value;
          });
        },
        onChanged: (String value) {
          print('field $i onChanged()');
          setState(() {
            model[i] = value;
          });
        },
        onSaved: (String value) {
          print('field $i onSaved()');
          setState(() {
            model[i] = value;
          });
        },
      );
      fields.add(widget);
    }

    fields.add(RaisedButton(
      child: Text('save'),
      onPressed: submitForm,
    ));

    return Form(
      key: _formKey,
      child: ListView(
        children: fields,
      ),
    );
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text('form test'),
        ),
        body: MyForm(),
      ),
    );
  }
}
2个回答

8
解决方案很简单,只需不使用 ListView,而是依靠 SingleChildScrollView(并将表单本身作为子级传递)。
import 'package:flutter/material.dart';

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

class MyForm extends StatefulWidget {
  @override
  State createState() {
    return _MyFormState();
  }
}

class _MyFormState extends State<MyForm> {
  static final GlobalKey<FormState> _formKey = GlobalKey();

  final Map<int, String> model = Map<int, String>();

  @override
  void initState() {
    print('initState()');
    for (int i = 0; i < 20; i++) {
      model[i] = null;
    }
    super.initState();
  }

  String validate(String value) {
    return value.isEmpty ? 'Cannot be blank!' : null;
  }

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

    if (formState.validate()) {
      print('form is valid');
      formState.save();
    } else {
      print('form is invalid');
    }
  }

  @override
  Widget build(BuildContext context) {
    List<Widget> fields = [];

    for (int i = 0; i < 20; i++) {
      Widget widget = TextFormField(
        initialValue: model[i],
        decoration: InputDecoration(
          labelText: 'field $i',
        ),
        validator: validate,
        onFieldSubmitted: (String value) {
          print('field $i onFieldSubmitted()');
          setState(() {
            model[i] = value;
          });
        },
      );
      fields.add(widget);
    }

    fields.add(RaisedButton(
      child: Text('save'),
      onPressed: submitForm,
    ));

    return SingleChildScrollView(
      child: Form(
        key: _formKey,
        child: Column(
          children: fields,
        ),
      ),
    );
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text('form test'),
        ),
        body: MyForm(),
      ),
    );
  }
}

0
我可以想到两种方法来实现它: 1)您不能使用验证器。使用提供程序的验证方法。如果提供程序返回错误,则显示 snackbar。 2)您可以使用验证器。您还必须在提供程序中使用验证方法。如果提供程序返回错误,请使用 scrollcontroller 滚动到所需的列表元素,希望表单字段验证器此时会被激活。

“Provider”是什么意思?是指这个 -> https://pub.dev/packages/provider 吗? - daveoncode
问题不在于验证逻辑,如果字段在视图中可见,则所有字段验证器都可以正常工作...关键是如果字段不可见,则FormStatevalidate()不会调用隐藏字段的验证器(换句话说,就像对表单的一部分进行验证而不是全部)。 - daveoncode
是的,正如我所写的那样,只要这些字段当前在视图中显示,验证就可以正常工作。 - daveoncode
我已经添加了一个完整的示例代码,演示了我的情况。 - daveoncode
仅使用验证方法是不够的。如果有错误,为了阻止表单提交,在submit()中需要停止继续执行。为此,每个表单字段都需要自己的TextEditingController(它是一个构造函数属性)。即使字段不在视图中,您也可以检查每个控制器是否具有其输入的文本。您可以在submit()中进行检查。由于没有直接的解决方案,我基本上会使用这种解决方法。我也会尝试它。 - Braj
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接