【问题标题】:Flutter: Widget communication : ToggleButtons in formsFlutter:小部件通信:表单中的切换按钮
【发布时间】:2021-07-01 09:00:42
【问题描述】:

我目前正在学习 Flutter 并取得了不错的进步,所以如果这是一个菜鸟问题,请多多包涵。

在我的应用程序中,我创建了一个页面以允许登录用户修改他们的个人资料。在这个页面中,我有一个 TextFormField(用于编辑电子邮件)和一个 ToggleButtons(有两个选项)来选择一个职业。

我使用 FutureBuilder 从数据库中检索我的数据,以便使用电子邮件预填充 TextFormField。我也有用户的职业。这些部分运行良好。

但是现在,当我尝试我的应用程序并按下(onPressed 事件)切换按钮来更改职业时,我的行为很糟糕。在 onPressed 事件中,我使用 setState() 来刷新小部件,以便每次更改专业时都从数据库中预先填充电子邮件字段。

我想知道如何处理这个案子?这样做的最佳做法是什么? 因为我需要这些值来提交表单。 我尝试拆分小部件以隔离切换按钮,但无法将所选值发送到父小部件。

谢谢!

编辑:添加代码

这是我的个人资料页面的代码:

class MyProfilePage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return MyProfilePageState();
  }
}

class MyProfilePageState extends State<MyProfilePage> {

  final FirebaseAuth auth = FirebaseAuth.instance;

  final StudentService StudentService = new StudentService();
  final TeacherService TeacherService = new TeacherService();


  final _formKey = GlobalKey<FormState>();

  Teacher _teacher;
  Student _student;

  User user;
  String uid;

  TextEditingController emailController = TextEditingController();

  @override
  void initState() {
    user = auth.currentUser;
    super.initState();
  }

  Future<bool> isProfileRetrieved() async {
    _student = await StudentService.getByUid(user.uid);
    _teacher = await TeacherService.getTeacherByUid(user.uid);
    fillFields();
    return true;
  }

  void fillFields() {
    emailController.text = _teacher == null ? _student.email : _teacher.email;
  }

  @override
  Widget build(BuildContext context) {
    return Theme(
      data: Theme.of(context).copyWith(primaryColor: primaryColor),
      child: Scaffold(
          appBar: AppBar(
            title: Row(mainAxisAlignment: MainAxisAlignment.center, children: [
              Image.asset(
                'assets/logo.png',
                fit: BoxFit.contain,
                height: 45,
              ),
              Container(
                  padding: const EdgeInsets.all(8.0),
                  child: Text('Teacher Monitoring')),
              Image.asset(
                'assets/logo.png',
                fit: BoxFit.contain,
                height: 45,
              ),
            ]),
            backgroundColor: Colors.black,
          ),
          body: FutureBuilder(
            future: isProfileRetrieved(),
            builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
              if (snapshot.hasData) {
                return Container(child: Form(
                    key: _formKey,
                    child: Column(children: <Widget>[
                      emailField(),
                      ProfessionWidget(),
                      saveButton()
                    ])));
              } else {
                return CircularProgressIndicator();
              }
            },)),
    );
  }

  

  Widget emailField() {
    return TextFormField(
      controller: emailController,
      decoration: const InputDecoration(
          hintText: 'Enter your email',
          prefixIcon: Icon(Icons.person),
          labelText: "Email"),
      validator: (String value) {
        if (value == null || value.isEmpty) {
          return 'Please enter some text';
        }
        return null;
      },
    );
  }

  Widget saveButton() {
    return SizedBox(
      width: double.infinity,
      child: ElevatedButton.icon(
        icon: Icon(Icons.save_alt),
        label: Text("Save"),
        style: ElevatedButton.styleFrom(
          primary: primaryColor, // background
          onPrimary: Colors.white, // foreground
        ),
        onPressed: () {
          if (_formKey.currentState.validate()) {
            ScaffoldMessenger.of(context)
                .showSnackBar(SnackBar(content: Text('Update Profile')));
          }
        },
      ),
    );
  }
}

这就是隔离 ToggleButton 的代码(这是我最后一次尝试解决我的问题):

class ProfessionWidget extends StatefulWidget {
  Teacher teacher;
  Student student;

  @override
  _ProfessionWidgetState createState() => _ProfessionWidgetState(teacher, student);
}

class _ProfessionWidgetState extends State<ProfessionWidget> {

  Teacher teacher;
  Student student;

  List<bool> isSelected;

  _ProfessionWidgetState(Teacher teacher, Student student);

  @override
  void initState() {
    super.initState();
    isSelected = [true, false];
  }
  @override
  Widget build(BuildContext context) {
    return professionField();
  }


  Future<bool> isProfileRetrieved(teacher, student) async {
    isSelected = [teacher != null, student != null];
    return true;
  }

  Widget professionField() {
    return Padding(
        padding: const EdgeInsets.only(top: 10.0),
        child: Column(children: <Widget>[
          Padding(
              padding: const EdgeInsets.only(bottom: 7.0),
              child: Text('Profession',
                  style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold))),
          ToggleButtons(
            borderColor: Colors.grey,
            selectedColor: Colors.white,
            fillColor: primaryColor,
            borderRadius: BorderRadius.circular(10),
            children: <Widget>[
              Padding(
                padding: const EdgeInsets.only(
                    top: 10.0, bottom: 10.0, right: 20.0, left: 20.0),
                child: Text(
                  'Teacher',
                  style: TextStyle(fontSize: 16),
                ),
              ),
              Padding(
                padding: const EdgeInsets.only(
                    top: 10.0, bottom: 10.0, right: 20.0, left: 20.0),
                child: Text(
                  'Student',
                  style: TextStyle(fontSize: 16),
                ),
              ),
            ],
            onPressed: (int index) {
              setState(() {
                for (int buttonIndex = 0;
                buttonIndex < isSelected.length;
                buttonIndex++) {
                  isSelected[buttonIndex] = buttonIndex == index;
                  if (buttonIndex == index) {
                    //TODO
                  }
                }
              });
            },
            isSelected: isSelected,
          )
        ]));
  }

}

【问题讨论】:

  • 您可以编辑您的问题并显示您的代码吗?这将使人们更容易以这种方式看到并帮助您。
  • 你是对的,我添加了代码:)
  • 解决此问题的最佳方法是创建扩展 FormField 的自定义小部件。这样做将允许您在顶层拥有一个Form,并在层次结构中拥有多个有状态的小部件。看看这篇文章:blog.bam.tech/developer-news/…,或者这个复选框的答案:stackoverflow.com/questions/53479942/checkbox-form-validation

标签: flutter dart


【解决方案1】:

来自FutureBuilder class documentation

未来一定是更早获得的,例如在 State.initState、State.didUpdateConfig 或 State.didChangeDependencies 期间。在构造 FutureBuilder 时,不能在 State.build 或 StatelessWidget.build 方法调用期间创建它。如果 Future 与 FutureBuilder 是同时创建的,那么每次 FutureBuilder 的 parent 重建时,异步任务都会重新启动。

因此,您需要将此 Future 作为变量存储在您的 State 中。使用此变量,您可以提供给 FutureBuilder 小部件。这样,您就不需要在每次重新构建屏幕时都重新创建 Future。

按照文档的建议,您可以在 initState 方法中启动未来:

class ProfessionWidget extends StatefulWidget {
[...]
  _ProfessionWidgetState createState() => _ProfessionWidgetState();
}

class _ProfessionWidgetState extends State<ProfessionWidget> {
final Future<bool> storedFuture; //1 - Create the variable.
[...]
  @override
  void initState() {
      super.initState();
      [...]
      storedFuture = isProfileRetrieved(); // 2 - On initState, call the method and keep the return value on the created variable.
  }

  @override
  Widget build(BuildContext context) {
[...]
    body: FutureBuilder(
            future: storedFuture, // 3 - Pass the stored variable to FutureBuilder.
            builder: (context, snapshot) {
[...]
              if (snapshot.hasData) {
                return Container();
              } else {
                return CircularProgressIndicator();
              }
            });
  }
}

【讨论】:

    猜你喜欢
    • 2020-11-07
    • 2021-08-12
    • 2011-09-09
    • 2020-12-01
    • 1970-01-01
    • 2020-12-26
    • 2022-08-19
    • 1970-01-01
    • 2019-07-11
    相关资源
    最近更新 更多