我对Ffltter/Dart完全陌生,我已经完成了我的应用程序的所有布局,现在是进行应用程序的API调用的时候了.我正试图尽可能干净地管理这些表单.

我创建了一个管理TextFields个数据(值和错误)的类,如果我的API返回错误,我希望在不调用setState(() {})的情况下更新屏幕,这可能吗?

此外,我的应用程序的许多屏幕使用用户实时输入的值,如果发生这种情况,我将不得不多次调用setState(() {})方法.

您知道如何处理对setState(() {})方法的过量调用吗?

我为demo创建了一个测试项目,以下是我的文件:

文件路径:/main.dart

import 'package:flutter/material.dart';
import 'login_form_data.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Test App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final LoginFormData _loginFormData = LoginFormData();

  void _submitLoginForm() {
    // Validate and then make a call to the login api
    // If the api returns any erros inject then in the LoginFormData class
    _loginFormData.setError('email', 'Invalid e-mail');

    setState(() {}); // Don't want to call setState
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Test App'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(30),
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              TextField(
                decoration: InputDecoration(
                  errorText: _loginFormData.firstError('email'),
                  labelText: 'E-mail',
                ),
                onChanged: (value) => _loginFormData.setValue('email', value),
              ),
              TextField(
                decoration: InputDecoration(
                  errorText: _loginFormData.firstError('password'),
                  labelText: 'Password',
                ),
                obscureText: true,
                onChanged: (value) =>
                    _loginFormData.setValue('password', value),
              ),
              ElevatedButton(
                onPressed: _submitLoginForm,
                child: const Text('Login'),
              )
            ],
          ),
        ),
      ),
    );
  }
}

文件路径:/login_form_data.dart

import 'form/form_data.dart';
import 'form/form_field.dart';

class LoginFormData extends FormData {
  @override
  Map<String, FormField> fields = {
    'email': FormField(),
    'password': FormField(),
    'simple_account': FormField(
      value: true,
    ),
  };

  LoginFormData();
}

文件路径:/form/form_data.dart

class FormData {
  final Map<String, dynamic> fields = {};

  dynamic getValue(
    String key, {
    String? defaultValue,
  }) {
    return fields[key]?.value ?? defaultValue;
  }

  void setValue(
    String key,
    String value,
  ) {
    fields[key].value = value;
  }

  void setError(
    String key,
    String error,
  ) {
    fields[key]?.errors.add(error);
  }

  dynamic firstError(
    String key,
  ) {
    return fields[key]?.errors.length > 0 ? fields[key]?.errors[0] : null;
  }

  FormData();
}

文件路径:/form/form_field.dart

class FormField {
  dynamic value;

  List errors = [];

  FormField({
    this.value,
  });
}

推荐答案

你本质上是在寻找一个State Management的解决方案. 有多种解决方案(您可以在此处阅读:https://docs.flutter.dev/development/data-and-backend/state-mgmt/options)

状态管理允许您声明您希望小部件更改状态的时间,而不必强制调用setState方法.

Ffltter推荐将Provider作为初学者解决方案,您可以在网上找到许多教程.

话虽如此,让我向您展示如何使用一个非常基本的解决方案实现此结果:更改通告程序 引用Flutter 文档:

"可以扩展或混合的类,提供更改 使用VoidCallback进行通知的通知API."

我们将使FormData成为一个更改通知程序,我们将让您的应用程序侦听实例上的更改,并基于这些更改重新构建自己. 步骤1: 根据您发布的代码,我可以断定您将基于父类FormData中的setValue和setError方法与LoginFormData交互.因此,我们将使FormData继承ChangeNotifer,并对这两个方法调用notfyListeners().

class FormData extends ChangeNotifier {
  final Map<String, dynamic> fields = {};

  dynamic getValue(
    String key, {
    String? defaultValue,
  }) {
    return fields[key]?.value ?? defaultValue;
  }

  void setValue(
    String key,
    String value,
  ) {
    fields[key].value = value;
    notifyListeners();
  }

  void setError(
    String key,
    String error,
  ) {
    fields[key]?.errors.add(error);
    notifyListeners();
  }

  dynamic firstError(
    String key,
  ) {
    return fields[key]?.errors.length > 0 ? fields[key]?.errors[0] : null;
  }

  FormData();
}

现在,每次调用setValue或setError时,FormData实例都会通知监听器. 第二步: 现在,我们必须在您的应用程序中设置一个小工具来监听这些更改.因为你的应用程序还很小,所以很容易找到一个地方来放置这个监听器.但是随着你的应用程序的发展,你会发现做这件事变得越来越困难,这就是像Provider这样的包派上用场的地方. 我们将使用AnimatedBuilder包装脚手架主体上的第一个填充小部件.尽管这个名字有误导性,但动画构建器并不局限于动画.它是一个小部件,它接收任何Listable对象作为参数,并在每次收到通知时重新构建自己,传递Listable的更新版本.

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final LoginFormData _loginFormData = LoginFormData();

  void _submitLoginForm() {
    // Validate and then make a call to the login api
    // If the api returns any erros inject then in the LoginFormData class
    _loginFormData.setError('email', 'Invalid e-mail');

    //setState(() {}); No longer necessary
  }

    @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Test App'),
      ),
      body: AnimatedBuilder(
        animation: _loginFormData,
        builder: (context, child) {
          return Padding(
            padding: const EdgeInsets.all(30),
            child: Center(
              child: Column(
                //... The rest of your widgets
              ),
            ),
          );
        }
      ),
    );
  }
}

Flutter相关问答推荐

Flutter bloc -如何使用BlocBuilder?

如何从外部控制有状态窗口小部件本身?

本机调试符号包含无效的目录调试符号.目前仅支持Android ABI

如何防止onTapCancel由子元素触发?

如何将Visual Studio代码中Flutter 应用的包名从com.example.XXX更改为Play Store中受限制的包名称

Ffmpeg_kit_fltter:^6.0.3版权问题

将记录/元组用作SplayTreeMap中的键

Flutter守护进程无法启动

Flutter类调用另一个类中的另一个方法失败

如何在 Dart/Flutter 中克隆 Future

当 Dart SDK 版本范围不匹配时,为什么依赖解析器不会抛出错误?

如何在Flutter 中使用选定的键和值从 map 列表创建 map

如何从Firebase登录获取用户信息,如电话号码、出生日期和性别

「Flutter」错误:没有名为'kind'的命名参数

如何更改 DropDownMenu 的显示方向?

Flutter可滚动定位列表支持rtl方向吗?

如何从 showModalBottomSheet 中的 topRight 和 topLeft 移除平边

如何在 Flutter 中将列小部件添加到行小部件?

Flutter 将方法应用于静态变量

如何将图像网络装箱到具有边界半径的容器中