Flutter表单输入简化插件easy_formz_inputs的使用

Flutter表单输入简化插件easy_formz_inputs的使用

easy_formz_inputs 库为 Dart 和 Flutter 应用程序提供了预构建且经过验证的输入字段。目前提供的输入字段包括电子邮件、密码、确认密码、URL、日期、电话和非空字段。

安装

要使用 easy_formz_inputs 库,首先在 pubspec.yaml 文件中添加相应的依赖:

dependencies:
  easy_formz_inputs: ^0.1.1

然后,在代码中导入库:

import 'package:easy_formz_inputs/easy_formz_inputs.dart';

使用

easy_formz_inputs 库提供了预构建且经过验证的输入字段。每个输入字段都定义为一个表单字段,可以用于验证和管理用户输入数据。

EmailInput

EmailInput 是一个表单字段,用于验证用户输入的电子邮件地址。使用它时,只需创建 EmailInput 类的一个实例:

final email = EmailInput.dirty();

然后可以在表单中使用 email 表单字段:

TextField(
  decoration: InputDecoration(labelText: 'Email'),
  onChanged: (value) {
    email.value = value;
  },
  keyboardType: TextInputType.emailAddress,
),

PasswordInput

PasswordInput 是一个表单字段,用于验证用户输入的密码长度和复杂性。使用它时,只需创建 PasswordInput 类的一个实例:

final password = PasswordInput.dirty();

然后可以在表单中使用 password 表单字段:

TextField(
  decoration: InputDecoration(labelText: 'Password'),
  onChanged: (value) {
    password.value = value;
  },
  obscureText: true,
),

ConfirmPasswordInput

ConfirmPasswordInput 是一个表单字段,用于比较两个密码字段以确保它们匹配。使用它时,只需创建 ConfirmPasswordInput 类的一个实例,并将原始密码字段作为参数传递:

final confirmPassword = ConfirmPasswordInput.dirty(password: password, value: '');

然后可以在表单中使用 confirmPassword 表单字段:

TextField(
  decoration: InputDecoration(labelText: 'Confirm password'),
  onChanged: (value) {
    confirmPassword.value = value;
  },
  obscureText: true,
),

UrlInput

UrlInput 是一个表单字段,用于验证用户输入的 URL。使用它时,只需创建 UrlInput 类的一个实例:

final url = UrlInput.dirty();

然后可以在表单中使用 url 表单字段:

TextField(
  decoration: InputDecoration(labelText: 'URL'),
  onChanged: (value) {
    url.value = value;
  },
  keyboardType: TextInputType.url,
),

NonEmptyInput

NonEmptyInput 是一个表单字段,用于验证用户是否输入了非空值。使用它时,只需创建 NonEmptyInput 类的一个实例:

final nonEmpty = NonEmptyInput.dirty();

然后可以在表单中使用 nonEmpty 表单字段:

TextField(
  decoration: InputDecoration(labelText: 'Non-empty field'),
  onChanged: (value) {
    nonEmpty.value = value;
  },
),

DateInput

DateInput 是一个表单字段,用于验证用户输入的日期格式,格式为 ‘DD/MM/YYYY’。

final dateInput = DateInput.dirty(value: '09/03/2022');

然后可以在表单中使用 dateInput 表单字段:

TextField(
  decoration: InputDecoration(labelText: 'Birthdate'),
  onChanged: (value) {
    dateInput.value = value;
  },
),

PhoneInput

PhoneInput 是一个表单字段,用于验证用户输入的电话号码。使用它时,只需创建 PhoneInput 类的一个实例,并传入国家信息:

final phoneInput = PhoneInput.dirty(value: '12312312',
  country: CountryModel(
      name: 'Venezuela, Bolivarian Republic of Venezuela',
      flag: '🇻🇪',
      code: 'VE',
      dialCode: '58',
      minLength: 10,
      maxLength: 10,
    )
);

然后可以在表单中使用 phoneInput 表单字段:

TextField(
  decoration: InputDecoration(labelText: 'Phone'),
  onChanged: (value) {
    phoneInput.value = value;
  },
),

完整示例

以下是一个完整的示例,展示了如何使用上述各个输入字段:

// 忽略对于文件的公共成员API文档和构造函数排序规则
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:formz/formz.dart';

import 'package:easy_formz_inputs/easy_formz_inputs.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Easy Formz Inputs Example')),
        body: const Padding(
          padding: EdgeInsets.all(24),
          child: SingleChildScrollView(child: MyForm()),
        ),
      ),
    );
  }
}

class MyForm extends StatefulWidget {
  const MyForm({Key? key}) : super(key: key);

  [@override](/user/override)
  State<MyForm> createState() => _MyFormState();
}

class _MyFormState extends State<MyForm> {
  final _key = GlobalKey<FormState>();
  final country = const CountryModel(
    name: 'Venezuela, Bolivarian Republic of Venezuela',
    flag: '🇻🇪',
    code: 'VE',
    dialCode: '58',
    minLength: 10,
    maxLength: 10,
  );
  late MyFormState _state;
  late final TextEditingController _emailController;
  late final TextEditingController _passwordController;
  late final TextEditingController _confirmPasswordController;
  late final TextEditingController _websiteController;
  late final TextEditingController _nameController;
  late final TextEditingController _phoneController;
  late final TextEditingController _birthdateController;

  void _onEmailChanged() {
    setState(() {
      _state = _state.copyWith(
          email: EmailInput.dirty(value: _emailController.text));
    });
  }

  void _onPasswordChanged() {
    setState(() {
      _state = _state.copyWith(
        password: PasswordInput.dirty(value: _passwordController.text),
      );
    });
  }

  void _onConfirmPasswordChanged() {
    setState(() {
      _state = _state.copyWith(
        confirmPassword: ConfirmPasswordInput.dirty(
            value: _confirmPasswordController.text,
            password: _passwordController.text),
      );
    });
  }

  void _onNameChanged() {
    setState(() {
      _state = _state.copyWith(
          name: NonEmptyInput.dirty(
        value: _nameController.text,
      ));
    });
  }

  void _onBirthdateChanged() {
    setState(() {
      _state = _state.copyWith(
          birthdate: DateInput.dirty(
        value: _birthdateController.text,
      ));
    });
  }

  void _onPhoneChanged() {
    setState(() {
      _state = _state.copyWith(
          phone: PhoneInput.dirty(
        value: _phoneController.text,
        country: country,
      ));
    });
  }

  Future<void> _onSubmit() async {
    if (!_key.currentState!.validate()) return;

    setState(() {
      _state = _state.copyWith(status: FormzSubmissionStatus.inProgress);
    });

    try {
      await _submitForm();
      _state = _state.copyWith(status: FormzSubmissionStatus.success);
    } catch (_) {
      _state = _state.copyWith(status: FormzSubmissionStatus.failure);
    }

    if (!mounted) return;

    setState(() {});

    FocusScope.of(context)
      ..nextFocus()
      ..unfocus();

    const successSnackBar = SnackBar(
      content: Text('Submitted successfully! 🎉'),
    );
    const failureSnackBar = SnackBar(
      content: Text('Something went wrong... 🚨'),
    );

    ScaffoldMessenger.of(context)
      ..hideCurrentSnackBar()
      ..showSnackBar(
        _state.status.isSuccess ? successSnackBar : failureSnackBar,
      );

    if (_state.status.isSuccess) _resetForm();
  }

  Future<void> _submitForm() async {
    await Future<void>.delayed(const Duration(seconds: 1));
    if (Random().nextInt(2) == 0) throw Exception();
  }

  void _resetForm() {
    _key.currentState!.reset();
    _emailController.clear();
    _passwordController.clear();
    setState(() => _state = MyFormState());
  }

  [@override](/user/override)
  void initState() {
    super.initState();
    _state = MyFormState();
    _emailController = TextEditingController(text: _state.email.value)
      ..addListener(_onEmailChanged);
    _passwordController = TextEditingController(text: _state.password.value)
      ..addListener(_onPasswordChanged);
    _confirmPasswordController =
        TextEditingController(text: _state.confirmPassword.value)
          ..addListener(_onConfirmPasswordChanged);
    _websiteController =
        TextEditingController(text: _state.confirmPassword.value)
          ..addListener(_onConfirmPasswordChanged);
    _phoneController = TextEditingController(text: _state.phone.value)
      ..addListener(_onPhoneChanged);
    _birthdateController = TextEditingController(text: _state.birthdate.value)
      ..addListener(_onBirthdateChanged);
    _nameController = TextEditingController(text: _state.name.value)
      ..addListener(_onNameChanged);
  }

  [@override](/user/override)
  void dispose() {
    _emailController.dispose();
    _passwordController.dispose();
    _confirmPasswordController.dispose();
    _nameController.dispose();
    _birthdateController.dispose();
    _phoneController.dispose();
    _websiteController.dispose();
    super.dispose();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Form(
      key: _key,
      child: Column(
        children: [
          TextFormField(
            controller: _nameController,
            decoration: const InputDecoration(
              icon: Icon(Icons.person),
              helperText: 'Name',
              helperMaxLines: 2,
              labelText: 'Name',
              errorMaxLines: 2,
            ),
            validator: (_) => _state.name.displayError.toString(),
            textInputAction: TextInputAction.done,
          ),
          Row(
            children: [
              InkWell(
                borderRadius: BorderRadius.circular(10),
                onTap: () async {},
                child: Padding(
                  padding: const EdgeInsets.only(left: 10, bottom: 10),
                  child: Row(
                    mainAxisSize: MainAxisSize.min,
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: <Widget>[
                      ClipRRect(
                        borderRadius: BorderRadius.circular(100),
                        child: Image.asset(
                          'assets/flags/${country.code.toLowerCase()}.png',
                          package: 'intl_phone_field',
                          width: 22,
                          height: 22,
                          fit: BoxFit.cover,
                        ),
                      ),
                      const SizedBox(width: 8),
                      FittedBox(
                        child: Text(
                          '+${country.dialCode}',
                          // style: widget.dropdownTextStyle,
                        ),
                      ),
                      const Icon(Icons.arrow_drop_down),
                      const SizedBox(width: 8),
                    ],
                  ),
                ),
              ),
              Expanded(
                child: TextFormField(
                  controller: _phoneController,
                  decoration: const InputDecoration(
                    helperText: 'Phone',
                    helperMaxLines: 2,
                    labelText: 'Phone',
                    errorMaxLines: 2,
                  ),
                  inputFormatters: [FilteringTextInputFormatter.digitsOnly],
                  validator: (_) => _state.phone.displayError.toString(),
                  textInputAction: TextInputAction.done,
                ),
              ),
            ],
          ),
          TextFormField(
            controller: _emailController,
            decoration: const InputDecoration(
              icon: Icon(Icons.email),
              labelText: 'Email',
              helperText: 'A valid email e.g. joe.doe@gmail.com',
            ),
            validator: (_) => _state.email.displayError.toString(),
            keyboardType: TextInputType.emailAddress,
            textInputAction: TextInputAction.next,
          ),
          TextFormField(
            controller: _passwordController,
            decoration: const InputDecoration(
              icon: Icon(Icons.lock),
              helperText:
                  'At least 8 characters including one letter and number',
              helperMaxLines: 2,
              labelText: 'Password',
              errorMaxLines: 2,
            ),
            validator: (_) => _state.password.displayError.toString(),
            obscureText: true,
            textInputAction: TextInputAction.done,
          ),
          TextFormField(
            controller: _confirmPasswordController,
            decoration: const InputDecoration(
              icon: Icon(Icons.lock),
              helperText: 'Confirm password',
              helperMaxLines: 2,
              labelText: 'Confirm Password',
              errorMaxLines: 2,
            ),
            validator: (_) => _state.confirmPassword.displayError.toString(),
            obscureText: true,
            textInputAction: TextInputAction.done,
          ),
          TextFormField(
            controller: _websiteController,
            decoration: const InputDecoration(
              icon: Icon(Icons.web),
              helperText: 'Website',
              helperMaxLines: 2,
              labelText: 'Website',
              errorMaxLines: 2,
            ),
            validator: (_) => _state.website.displayError.toString(),
            textInputAction: TextInputAction.done,
          ),
          TextFormField(
            controller: _birthdateController,
            decoration: const InputDecoration(
              hintText: 'DD/MM/YYYY',
              icon: Icon(
                Icons.calendar_month,
              ),
            ),
            keyboardType: TextInputType.datetime,
            inputFormatters: [
              FilteringTextInputFormatter.digitsOnly,
              LengthLimitingTextInputFormatter(8),
              DateTextFormatter()
            ],
            validator: (_) => _state.birthdate.displayError.toString(),
            textInputAction: TextInputAction.next,
          ),
          const SizedBox(height: 24),
          if (_state.status.isInProgress)
            const CircularProgressIndicator()
          else
            ElevatedButton(
              onPressed: _onSubmit,
              child: const Text('Submit'),
            ),
        ],
      ),
    );
  }
}

class MyFormState with FormzMixin {
  MyFormState({
    this.email = const EmailInput.pure(),
    this.password = const PasswordInput.pure(),
    this.confirmPassword = const ConfirmPasswordInput.pure(),
    this.website = const UrlInput.pure(),
    this.birthdate = const DateInput.pure(),
    this.phone = const PhoneInput.pure(),
    this.name = const NonEmptyInput.pure(),
    this.status = FormzSubmissionStatus.initial,
  });

  final EmailInput email;
  final PasswordInput password;
  final ConfirmPasswordInput confirmPassword;
  final UrlInput website;
  final DateInput birthdate;
  final PhoneInput phone;
  final NonEmptyInput name;
  final FormzSubmissionStatus status;

  MyFormState copyWith({
    EmailInput? email,
    PasswordInput? password,
    ConfirmPasswordInput? confirmPassword,
    UrlInput? website,
    DateInput? birthdate,
    PhoneInput? phone,
    NonEmptyInput? name,
    FormzSubmissionStatus? status,
  }) {
    return MyFormState(
      email: email ?? this.email,
      password: password ?? this.password,
      confirmPassword: confirmPassword ?? this.confirmPassword,
      website: website ?? this.website,
      birthdate: birthdate ?? this.birthdate,
      phone: phone ?? this.phone,
      name: name ?? this.name,
      status: status ?? this.status,
    );
  }

  [@override](/user/override)
  List<FormzInput<dynamic, dynamic>> get inputs => [email, password];
}

class DateTextFormatter extends TextInputFormatter {
  static const _maxChars = 8;

  [@override](/user/override)
  TextEditingValue formatEditUpdate(
    TextEditingValue oldValue,
    TextEditingValue newValue,
  ) {
    const separator = '/';
    final text = _format(
      newValue.text,
      oldValue.text,
      separator,
    );

    return newValue.copyWith(
      text: text,
      selection: updateCursorPosition(
        oldValue,
        text,
      ),
    );
  }

  String _format(
    String value,
    String oldValue,
    String separator,
  ) {
    final isErasing = value.length < oldValue.length;
    final isComplete = value.length > _maxChars + 2;

    if (!isErasing && isComplete) {
      return oldValue;
    }

    value = value.replaceAll(separator, '');
    final result = <String>[];

    for (var i = 0; i < min(value.length, _maxChars); i++) {
      result.add(value[i]);
      if ((i == 1 || i == 3) && i != value.length - 1) {
        result.add(separator);
      }
    }

    return result.join();
  }

  TextSelection updateCursorPosition(
    TextEditingValue oldValue,
    String text,
  ) {
    final endOffset = max(
      oldValue.text.length - oldValue.selection.end,
      0,
    );

    final selectionEnd = text.length - endOffset;

    return TextSelection.fromPosition(TextPosition(offset: selectionEnd));
  }
}

更多关于Flutter表单输入简化插件easy_formz_inputs的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter表单输入简化插件easy_formz_inputs的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter项目中使用easy_formz_inputs插件来简化表单输入的示例代码。这个插件通常与Formz库一起使用,以更好地管理表单状态和验证。

首先,确保在你的pubspec.yaml文件中添加必要的依赖项:

dependencies:
  flutter:
    sdk: flutter
  formz: ^x.y.z  # 替换为最新版本号
  easy_formz_inputs: ^x.y.z  # 替换为最新版本号

然后,运行flutter pub get来安装这些依赖项。

示例代码

以下是一个完整的示例,展示了如何使用easy_formz_inputs来创建一个简单的登录表单。

1. 定义表单模型

首先,我们需要定义表单的模型。使用Formz库来创建表单状态和输入类。

// models/authentication_form.dart
import 'package:formz/formz.dart';

enum AuthenticationFormError {
  invalidEmail,
  invalidPassword,
}

class AuthenticationForm extends FormzInput<Map<String, String>, AuthenticationFormError> {
  AuthenticationForm() : super(Map.fromEntries([
    MapEntry('email', ''),
    MapEntry('password', ''),
  ]));

  String get email => value['email'] ?? '';

  String get password => value['password'] ?? '';

  AuthenticationForm.from(Map<String, String> value) : super(value);

  @override
  Map<AuthenticationFormError, String> validate() {
    final errors = <AuthenticationFormError, String>{};

    if (!RegExp(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").hasMatch(email)) {
      errors[AuthenticationFormError.invalidEmail] = 'Email must be valid.';
    }

    if (password.length < 6) {
      errors[AuthenticationFormError.invalidPassword] = 'Password must be at least 6 characters.';
    }

    return errors;
  }
}

2. 创建UI

接下来,我们创建UI并使用easy_formz_inputs来简化表单输入。

// main.dart
import 'package:flutter/material.dart';
import 'package:easy_formz_inputs/easy_formz_inputs.dart';
import 'models/authentication_form.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: AuthenticationScreen(),
    );
  }
}

class AuthenticationScreen extends StatefulWidget {
  @override
  _AuthenticationScreenState createState() => _AuthenticationScreenState();
}

class _AuthenticationScreenState extends State<AuthenticationScreen> {
  final _form = AuthenticationForm();

  void _submit() {
    if (_form.validate().isEmpty) {
      // Handle valid form submission
      print('Form submitted: $_form.value');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Authentication'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            EasyFormzTextFormField<AuthenticationForm, AuthenticationFormError>(
              form: _form,
              field: 'email',
              label: 'Email',
              errorMessages: {
                AuthenticationFormError.invalidEmail: 'Email must be valid.',
              },
              keyboardType: TextInputType.emailAddress,
              autovalidateMode: AutovalidateMode.onUserInteraction,
            ),
            SizedBox(height: 16),
            EasyFormzTextFormField<AuthenticationForm, AuthenticationFormError>(
              form: _form,
              field: 'password',
              label: 'Password',
              errorMessages: {
                AuthenticationFormError.invalidPassword: 'Password must be at least 6 characters.',
              },
              obscureText: true,
              autovalidateMode: AutovalidateMode.onUserInteraction,
            ),
            SizedBox(height: 24),
            ElevatedButton(
              onPressed: _submit,
              child: Text('Submit'),
            ),
          ],
        ),
      ),
    );
  }
}

解释

  1. 定义表单模型:我们创建了一个AuthenticationForm类,它继承自FormzInput。我们定义了两个字段emailpassword,并实现了验证逻辑。

  2. 创建UI:我们使用EasyFormzTextFormField来创建表单输入字段。这个组件自动处理与Formz模型的交互,包括验证和显示错误信息。

  3. 提交表单:当用户点击提交按钮时,我们检查表单是否有效,并在控制台中打印表单值。

这个示例展示了如何使用easy_formz_inputs来简化表单输入和验证过程。你可以根据需要进一步扩展和自定义这个示例。

回到顶部