Flutter 用 provider 和 riverpod 制作表单

Posted

技术标签:

【中文标题】Flutter 用 provider 和 riverpod 制作表单【英文标题】:Flutter make a form with provider and riverpod 【发布时间】:2021-02-07 08:34:49 【问题描述】:

我是 Flutter 的新手,我想升级我的代码。我有一个使用多个 textformfields 的表单,我想使用 provider 和 riverpod 转换此代码以提高可读性,但我不知道该怎么做。 例如,我将代码简化为只有一个距离场,但还有很多其他的。

这是我的 CalculatorScreen :

import 'dart:async' show Future;
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:app/core/models/model_form_calculator.dart';
import 'package:app/core/services/service_form_validator.dart';
import 'package:app/core/utils/utils_app_color.dart';

class CalculatorScreen extends StatefulWidget

  CalculatorScreen(Key key) : super(key: key);

  @override
  _CalculatorScreenState createState() => _CalculatorScreenState();


class _CalculatorScreenState extends State<CalculatorScreen>

  final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
  final _formKey = GlobalKey<FormState>();

  FormCalculatorModel _formData = FormCalculatorModel();
  bool _autoValidateForm = false;

  final TextEditingController _controllerDistance = TextEditingController();

  @override
  void initState() 
    super.initState();
  

  @override
  void dispose()
  
    _controllerDistance.dispose();
    super.dispose();
  

  @override
  Widget build(BuildContext context)
  
    return GestureDetector(
      onTap: (() => FocusScope.of(context).requestFocus(FocusNode())),
      child: Scaffold(
          key: _scaffoldKey,
          backgroundColor: AppColors.colorBgDark,
          body : _buildBody()
      ),
    );
  

  Widget _buildBody()
  
    return SingleChildScrollView(
      child: Column(
        children: [
          Form(
            key: _formKey,
            autovalidate: _autoValidateForm,
            child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  TextFormField(
                    controller: _controllerDistance,
                    keyboardType: TextInputType.number,
                    decoration: InputDecoration(
                      hintText: "Enter a value",
                    ),
                    validator: (value)
                      return FormValidatorService.isDistanceValid(value);
                    ,
                    onSaved: (var value) 
                      _formData.distance = num.tryParse(value).round();
                    ,
                  ),
                  Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: [
                      Expanded(
                        child: FlatButton(
                            child: Text("Erase"),
                            onPressed: _buttonResetAction
                        ),
                      ),
                      Expanded(
                        child: FlatButton(
                            child: Text("Send"),
                            onPressed: _buttonSubmitAction
                        ),
                      ),
                    ],
                  ),
                ]
            ),
          ),
        ],
      ),
    );
  

  void _buttonResetAction()
  
    _eraseForm();
  

  void _eraseForm()
    setState(() 
      _formKey.currentState.reset();
      _formData = FormCalculatorModel();
      _autoValidateForm = false;
      _controllerDistance.clear();
    );
  

  void _buttonSubmitAction() async
  
    if (!_formKey.currentState.validate()) 
      setState(() 
        _autoValidateForm = true;
      );
      return;
    
    _formKey.currentState.save();

    try
      // some actions
    catch(e)
      _eraseForm();
      print(e.toString());
    
  

This is my formModel(此模型包含我可以填写表单的所有字段,并允许我存储表单的值,一旦验证,然后使用这些值进行计算 ):


class FormCalculatorModel
  int distance;

  FormCalculatorModel(
    this.distance,
 
  );

  @override
  String toString() 
    return ' '
        '$this.distance, '
    '';
  


还有我的 FormValidatorService :

class FormValidatorService

  static String isDistanceValid(String value)
  
    num _distance = num.tryParse(value);
    if (_distance == null) 
      return "is required";
    
    if (_distance < 200) 
      return "Min distance is 200";
    
    if (_distance > 1000) 
      return "Max dist is 1000";
    
    return null;
  

现在我想用riverpod 转换它。我有点迷茫,互联网上的例子很少,我真的不知道如何管理我的表格 起初我只是尝试处理表单的验证,但它不起作用。

我的计算器屏幕:

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

class CalculatorScreen extends HookWidget

  final _formKey = GlobalKey<FormState>();
  bool _autoValidateForm = false;
  FormCalculatorModel _formData = FormCalculatorModel();
  final TextEditingController _controllerDistance = TextEditingController();

  @override
  Widget build(BuildContext context) 
    return GestureDetector(
      onTap: (() => FocusScope.of(context).requestFocus(FocusNode())),
      child: Scaffold(
          body : _buildBody(context)
      ),
    );
  

  Widget _buildBody(BuildContext context)

    final _formModel = useProvider(formCalculatorProvider.state);

    return SingleChildScrollView(
      child: Column(
        children: [
          TitleComponent(
            title: "Calcul de charge",
            description: "Parametrer l'environnement de tir",
          ),
          ContainerComponent(
            background: AppColors.colorBgLight,
            children: [
              Form(
                key : _formKey,
                autovalidate: _autoValidateForm,
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    TextFormField(
                      decoration: InputDecoration(
                        labelText: "Distance",
                        //errorText: _formModel.distance.error,
                      ),
                      controller: _controllerDistance,
                      validator: (String value)
                        return FormValidatorService.isDistanceValid(value);
                      ,
                      onSaved: (var value) _formData.distance = num.tryParse(value).round();
                    ),
                  ],
                ),
              ),
              ButtonComponent.primary(
                  text: "Calculer",
                  context: context,
                  onPressed : context.read(formCalculatorProvider).submitData(key: _formKey),
              ),
            ],
          )
        ],
      ),
    );
  


还有我的 FormCalculatorNotifier :

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

enum FormState

  EMPTY,
  SUCCESS,
  ERROR


class FormCalculatorModelNew 
  const FormCalculatorModelNew(this.formState, this.autoValidate, this.distance);
  final FormState formState;
  final bool autoValidate;
  final String distance;


class FormCalculatorNotifier extends StateNotifier<FormCalculatorModelNew>

  FormCalculatorNotifier() : super(_initial);

  static const FormState _initialState = FormState.EMPTY;
  static const _initial = FormCalculatorModelNew(
      formState : _initialState,
      autoValidate: false,
      distance: null
  );

   submitData(key)
     print(key);
     if (!key.currentState.validate()) 
       state = FormCalculatorModelNew(
           autoValidate: true,
       );
       return;
     
     key.currentState.save();
  


提供者:

final formCalculatorProvider = StateNotifierProvider((ref) => FormCalculatorNotifier());

【问题讨论】:

这方面有什么进展吗?您还可以添加验证。 【参考方案1】:

在您的示例代码中使用 Provider 并没有什么意义,因为我看不到任何地方监听 formCalculatorProvider 的状态。此外,表单本身应在表单小部件本身中进行管理。

我假设您想与其他小部件共享距离值。以下是我将要做的:

_autoValidate: 留在widget里面,用Hook处理 在FormCalculatorModelNew中添加copyWith(可以轻松更新部分值)

formCalculatorProvider部分:

final formCalculatorProvider = StateNotifierProvider((ref) => FormCalculatorNotifier());

enum MyFormState  EMPTY, SUCCESS, ERROR 

class FormCalculatorModelNew 
  const FormCalculatorModelNew(this.formState, this.distance);

  final MyFormState formState;
  final int distance;

  FormCalculatorModelNew copyWith(
    MyFormState formState,
    int distance,
  ) 
    return FormCalculatorModelNew(
      formState: formState ?? this.formState,
      distance: distance ?? this.distance,
    );
  


class FormCalculatorNotifier extends StateNotifier<FormCalculatorModelNew> 
  FormCalculatorNotifier() : super(_initial);

  static const MyFormState _initialState = MyFormState.EMPTY;
  static const _initial =
      FormCalculatorModelNew(formState: _initialState, distance: null);

  void update(int distance) 
    state = state.copyWith(distance: distance, formState: MyFormState.SUCCESS);
  

  void error() 
    state = state.copyWith(distance: null, formState: MyFormState.ERROR);
  

  void clear() 
    state = state.copyWith(distance: null, formState: MyFormState.EMPTY);
  

CalculatorScreen部分:(简化)

class CalculatorScreen extends HookWidget 
  final _formKey = GlobalKey<FormState>();

  @override
  Widget build(BuildContext context) 
    final _autoValidate = useState<bool>(false);
    final _controller = useTextEditingController();

    return Scaffold(
      body: Form(
        key: _formKey,
        autovalidate: _autoValidate.value,
        child: Column(
          children: [
            TextFormField(
              controller: _controller,
              keyboardType: TextInputType.number,
              validator: (value) 
                return FormValidatorService.isDistanceValid(value);
              ,
              onSaved: (value) 
                context.read(formCalculatorProvider).update(num.tryParse(value).round());
              ,
            ),
            Row(
              children: [
                FlatButton(
                  child: Text('Erase'),
                  onPressed: () 
                    _formKey.currentState.reset();
                    _controller.clear();
                    _autoValidate.value = false;
                    context.read(formCalculatorProvider).clear();
                  ,
                ),
                FlatButton(
                  child: Text('Send'),
                  onPressed: () 
                    if(_formKey.currentState.validate())
                      _formKey.currentState.save();
                    else
                      _autoValidate.value = true;
                      context.read(formCalculatorProvider).error();
                    
                  ,
                ),
              ],
            ),
          ],
        ),
      ),
    );
  

【讨论】:

想象一下我们正在逐步处理表单(使用步进式工作流程)在父级中呈现具有不同值的相同小部件并使用 Riverpod 处理其状态。【参考方案2】:

您可以使用TextEditingController。

像这样进一步创建一个提供程序,您现在可以收听文本更改并使用相同的提供程序将它们存储在所需的位置

final formControllerProvider =
StateProvider<TextEditingController>((ref) => TextEditingController());

【讨论】:

如何将formControllerProvider生成的控制器分配给“TextFormField”控制器字段? 我认为这不能正确处理文本编辑更新,您需要改用ChangeNotifierProvider 吗?

以上是关于Flutter 用 provider 和 riverpod 制作表单的主要内容,如果未能解决你的问题,请参考以下文章

Flutter Flare, Rive,它可以用作背景吗?

如何在颤动中播放 Rive 动画

Flutter 用 provider 和 riverpod 制作表单

有啥方法可以用 Provider 在 Flutter 中全局登录学生吗?

Flutter Provider - 在值更改时调用函数而不调用 build()

Flutter 动画,Flare VS GIF