Flutter表单管理插件flutter_form_bloc的使用
Flutter表单管理插件flutter_form_bloc的使用
简介
flutter_form_bloc
是一个用于Flutter应用中轻松实现表单状态管理的插件。它基于BLoC(Business Logic Component)模式,将表单的状态和业务逻辑从业务界面中分离出来,从而简化了表单处理流程,并提高了代码的可维护性和复用性。
特性
- ✅ 同步字段验证
- ✅ 异步字段验证
- ✅ 易于加载和初始化
- ✅ 向导/分步表单
- ✅ 提交进度跟踪
- ✅ 成功/失败响应
- ✅ 可序列化表单
- ✅ 将提交错误映射到字段
- ✅ 动态字段支持
- ✅ 条件字段支持
- ✅ 列表字段支持
- ✅ 分组字段支持
- ✅ CRUD操作支持
- ✅ 内置美观的小部件
安装与配置
在您的pubspec.yaml
文件中添加依赖项:
dependencies:
flutter_form_bloc: ^最新版本号
确保您查看官方文档获取最新的安装指南及教程。
示例代码
下面是一个完整的示例应用程序,展示了如何使用flutter_form_bloc
创建包含多种输入控件的表单。
main.dart
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_form_bloc/flutter_form_bloc.dart';
void main() {
runApp(const App());
}
class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
inputDecorationTheme: InputDecorationTheme(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(20),
),
),
),
builder: (context, child) {
return FormThemeProvider(
theme: FormTheme(
checkboxTheme: CheckboxFieldTheme(
canTapItemTile: true,
),
radioTheme: RadioFieldTheme(
canTapItemTile: true,
),
),
child: child!,
);
},
home: AllFieldsForm(),
);
}
}
// 表单逻辑部分
class AllFieldsFormBloc extends FormBloc<String, String> {
// 定义各个字段
final text1 = TextFieldBloc();
final boolean1 = BooleanFieldBloc();
final boolean2 = BooleanFieldBloc();
final select1 = SelectFieldBloc(
items: ['Option 1', 'Option 2'],
validators: [FieldBlocValidators.required],
);
final select2 = SelectFieldBloc(
items: ['Option 1', 'Option 2'],
validators: [FieldBlocValidators.required],
);
final multiSelect1 = MultiSelectFieldBloc<String, dynamic>(
items: [
'Option 1',
'Option 2',
'Option 3',
'Option 4',
'Option 5',
],
);
final file = InputFieldBloc<File?, String>(initialValue: null);
final date1 = InputFieldBloc<DateTime?, Object>(initialValue: null);
final dateAndTime1 = InputFieldBloc<DateTime?, Object>(initialValue: null);
final time1 = InputFieldBloc<TimeOfDay?, Object>(initialValue: null);
final double1 = InputFieldBloc<double, dynamic>(initialValue: 0.5);
// 构造函数中注册所有字段
AllFieldsFormBloc() : super(autoValidate: false) {
addFieldBlocs(fieldBlocs: [
text1,
boolean1,
boolean2,
select1,
select2,
multiSelect1,
date1,
dateAndTime1,
time1,
double1,
]);
}
void addErrors() {
// 添加错误信息给各个字段
text1.addFieldError('Awesome Error!');
boolean1.addFieldError('Awesome Error!');
boolean2.addFieldError('Awesome Error!');
select1.addFieldError('Awesome Error!');
select2.addFieldError('Awesome Error!');
multiSelect1.addFieldError('Awesome Error!');
date1.addFieldError('Awesome Error!');
dateAndTime1.addFieldError('Awesome Error!');
time1.addFieldError('Awesome Error!');
}
@override
void onSubmitting() async {
try {
await Future<void>.delayed(Duration(milliseconds: 500));
emitSuccess(canSubmitAgain: true);
} catch (e) {
emitFailure();
}
}
}
// UI展示部分
class AllFieldsForm extends StatelessWidget {
const AllFieldsForm({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => AllFieldsFormBloc(),
child: Builder(
builder: (context) {
final formBloc = BlocProvider.of<AllFieldsFormBloc>(context);
return Scaffold(
appBar: AppBar(title: Text('Built-in Widgets')),
floatingActionButton: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
FloatingActionButton.extended(
heroTag: null,
onPressed: formBloc.addErrors,
icon: Icon(Icons.error_outline),
label: Text('ADD ERRORS'),
),
SizedBox(height: 12),
FloatingActionButton.extended(
heroTag: null,
onPressed: formBloc.submit,
icon: Icon(Icons.send),
label: Text('SUBMIT'),
),
],
),
body: FormBlocListener<AllFieldsFormBloc, String, String>(
onSubmitting: (context, state) {
LoadingDialog.show(context);
},
onSuccess: (context, state) {
LoadingDialog.hide(context);
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => SuccessScreen()));
},
onFailure: (context, state) {
LoadingDialog.hide(context);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(state.failureResponse!)));
},
child: ScrollableFormBlocManager(
formBloc: formBloc,
child: SingleChildScrollView(
physics: ClampingScrollPhysics(),
padding: EdgeInsets.all(24.0),
child: Column(
children: <Widget>[
// 文本框
TextFieldBlocBuilder(
textFieldBloc: formBloc.text1,
suffixButton: SuffixButton.obscureText,
decoration: InputDecoration(
labelText: 'TextFieldBlocBuilder',
prefixIcon: Icon(Icons.text_fields),
),
),
// 单选按钮
RadioButtonGroupFieldBlocBuilder<String>(
selectFieldBloc: formBloc.select2,
decoration: InputDecoration(
labelText: 'RadioButtonGroupFieldBlocBuilder',
),
groupStyle: FlexGroupStyle(),
itemBuilder: (context, item) => FieldItem(
child: Text(item),
),
),
// 复选框
CheckboxGroupFieldBlocBuilder<String>(
multiSelectFieldBloc: formBloc.multiSelect1,
decoration: InputDecoration(
labelText: 'CheckboxGroupFieldBlocBuilder',
),
groupStyle: ListGroupStyle(
scrollDirection: Axis.horizontal,
height: 64,
),
itemBuilder: (context, item) => FieldItem(
child: Text(item),
),
),
// 日期选择器
DateTimeFieldBlocBuilder(
dateTimeFieldBloc: formBloc.date1,
format: DateFormat('dd-MM-yyyy'),
initialDate: DateTime.now(),
firstDate: DateTime(1900),
lastDate: DateTime(2100),
decoration: InputDecoration(
labelText: 'DateTimeFieldBlocBuilder',
prefixIcon: Icon(Icons.calendar_today),
helperText: 'Date',
),
),
// 日期时间选择器
DateTimeFieldBlocBuilder(
dateTimeFieldBloc: formBloc.dateAndTime1,
canSelectTime: true,
format: DateFormat('dd-MM-yyyy hh:mm'),
initialDate: DateTime.now(),
firstDate: DateTime(1900),
lastDate: DateTime(2100),
decoration: InputDecoration(
labelText: 'DateTimeFieldBlocBuilder',
prefixIcon: Icon(Icons.date_range),
helperText: 'Date and Time',
),
),
// 时间选择器
TimeFieldBlocBuilder(
timeFieldBloc: formBloc.time1,
format: DateFormat('hh:mm a'),
initialTime: TimeOfDay.now(),
decoration: InputDecoration(
labelText: 'TimeFieldBlocBuilder',
prefixIcon: Icon(Icons.access_time),
),
),
// 开关
SwitchFieldBlocBuilder(
booleanFieldBloc: formBloc.boolean2,
body: Text('SwitchFieldBlocBuilder'),
),
// 下拉菜单
DropdownFieldBlocBuilder<String>(
selectFieldBloc: formBloc.select1,
decoration: InputDecoration(
labelText: 'DropdownFieldBlocBuilder',
),
itemBuilder: (context, value) => FieldItem(
isEnabled: value != 'Option 1',
child: Text(value),
),
),
Row(
children: [
IconButton(
onPressed: () => formBloc.addFieldBloc(
fieldBloc: formBloc.select1),
icon: Icon(Icons.add),
),
IconButton(
onPressed: () => formBloc.removeFieldBloc(
fieldBloc: formBloc.select1),
icon: Icon(Icons.delete),
),
],
),
// 复选框
CheckboxFieldBlocBuilder(
booleanFieldBloc: formBloc.boolean1,
body: Text('CheckboxFieldBlocBuilder'),
),
CheckboxFieldBlocBuilder(
booleanFieldBloc: formBloc.boolean1,
body: Text('CheckboxFieldBlocBuilder trailing'),
controlAffinity: FieldBlocBuilderControlAffinity.trailing,
),
// 滑块
SliderFieldBlocBuilder(
inputFieldBloc: formBloc.double1,
divisions: 10,
labelBuilder: (context, value) =>
value.toStringAsFixed(2),
),
SliderFieldBlocBuilder(
inputFieldBloc: formBloc.double1,
divisions: 10,
labelBuilder: (context, value) =>
value.toStringAsFixed(2),
activeColor: Colors.red,
inactiveColor: Colors.green,
),
ChoiceChipFieldBlocBuilder<String>(
selectFieldBloc: formBloc.select2,
itemBuilder: (context, value) => ChipFieldItem(
label: Text(value),
),
),
FilterChipFieldBlocBuilder<String>(
multiSelectFieldBloc: formBloc.multiSelect1,
itemBuilder: (context, value) => ChipFieldItem(
label: Text(value),
),
),
BlocBuilder<InputFieldBloc<File?, String>,
InputFieldBlocState<File?, String>>(
bloc: formBloc.file,
builder: (context, state) {
return Container();
})
],
),
),
),
),
);
},
),
);
}
}
// 加载对话框
class LoadingDialog extends StatelessWidget {
static void show(BuildContext context, {Key? key}) =>
showDialog<void>(
context: context,
useRootNavigator: false,
barrierDismissible: false,
builder: (_) => LoadingDialog(key: key),
).then((_) => FocusScope.of(context).requestFocus(FocusNode()));
static void hide(BuildContext context) => Navigator.pop(context);
const LoadingDialog({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async => false,
child: Center(
child: Card(
child: Container(
width: 80,
height: 80,
padding: EdgeInsets.all(12.0),
child: CircularProgressIndicator(),
),
),
),
);
}
}
// 成功页面
class SuccessScreen extends StatelessWidget {
const SuccessScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.tag_faces, size: 100),
SizedBox(height: 10),
Text(
'Success',
style: TextStyle(fontSize: 54, color: Colors.black),
textAlign: TextAlign.center,
),
SizedBox(height: 10),
ElevatedButton.icon(
onPressed: () => Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => const AllFieldsForm())),
icon: Icon(Icons.replay),
label: Text('AGAIN'),
),
],
),
),
);
}
}
该示例展示了如何定义不同类型的表单字段、设置验证规则、处理表单提交事件以及根据表单状态更新UI。通过这个例子,您可以了解如何利用flutter_form_bloc
来构建复杂且功能丰富的表单界面。
更多关于Flutter表单管理插件flutter_form_bloc的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter表单管理插件flutter_form_bloc的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,flutter_form_bloc
是一个强大的 Flutter 库,用于管理和验证表单状态。它基于 BLoC(Business Logic Component)架构模式,使得表单状态管理更加清晰和模块化。
以下是一个简单的代码示例,展示了如何使用 flutter_form_bloc
来管理一个登录表单。
1. 添加依赖
首先,在你的 pubspec.yaml
文件中添加 flutter_form_bloc
和 flutter_bloc
依赖:
dependencies:
flutter:
sdk: flutter
flutter_bloc: ^8.0.0
flutter_form_bloc: ^3.0.0
2. 创建表单字段枚举
定义一个枚举来表示表单字段:
enum LoginFormFields { email, password }
3. 创建表单字段初始值
定义表单字段的初始值:
final initialLoginFormState = LoginFormState(
email: Formz.empty(LoginFormFields.email.toString()),
password: Formz.empty(LoginFormFields.password.toString()),
);
4. 创建表单状态类
创建一个类来表示表单的状态:
import 'package:formz/formz.dart';
class LoginFormState extends FormzStatus {
final FormzField<String> email;
final FormzField<String> password;
LoginFormState({
required this.email,
required this.password,
Status? status,
}) : super(status ?? Status.pure);
@override
List<Object?> get props => [email, password, status];
LoginFormState copyWith({
FormzField<String>? email,
FormzField<String>? password,
Status? status,
}) {
return LoginFormState(
email: email ?? this.email,
password: password ?? this.password,
status: status ?? this.status,
);
}
}
5. 创建 BLoC 类
定义一个 BLoC 类来处理表单的逻辑:
import 'package:bloc/bloc.dart';
import 'package:flutter_form_bloc/flutter_form_bloc.dart';
import 'package:equatable/equatable.dart';
part 'login_form_event.dart';
part 'login_form_state.dart';
class LoginFormBloc extends Bloc<LoginFormEvent, LoginFormState> {
LoginFormBloc() : super(initialLoginFormState);
@override
Stream<LoginFormState> mapEventToState(
LoginFormEvent event,
) async* {
if (event is EmailChanged) {
yield* _mapEmailChangedToState(event.email);
} else if (event is PasswordChanged) {
yield* _mapPasswordChangedToState(event.password);
} else if (event is Submitted) {
yield* _mapFormSubmittedToState();
}
}
Stream<LoginFormState> _mapEmailChangedToState(String email) async* {
final emailField = FormzField.from(email);
yield state.copyWith(
email: emailField,
status: Formz.validate([state.email, state.password]),
);
}
Stream<LoginFormState> _mapPasswordChangedToState(String password) async* {
final passwordField = FormzField.from(password);
yield state.copyWith(
password: passwordField,
status: Formz.validate([state.email, state.password]),
);
}
Stream<LoginFormState> _mapFormSubmittedToState() async* {
if (state.status == Status.valid) {
// Perform login action
yield state.copyWith(status: Status.submitting);
}
}
}
6. 定义事件类
定义事件类来表示用户交互:
// login_form_event.dart
part of 'login_form_bloc.dart';
abstract class LoginFormEvent extends Equatable {
const LoginFormEvent();
@override
List<Object?> get props => [];
}
class EmailChanged extends LoginFormEvent {
final String email;
const EmailChanged({required this.email});
@override
List<Object?> get props => [email];
}
class PasswordChanged extends LoginFormEvent {
final String password;
const PasswordChanged({required this.password});
@override
List<Object?> get props => [password];
}
class Submitted extends LoginFormEvent {}
7. 创建 UI 层
使用 FlutterFormBloc
小部件来构建表单:
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_form_bloc/flutter_form_bloc.dart';
import 'login_form_bloc.dart';
void main() {
runApp(
BlocProvider<LoginFormBloc>(
create: (context) => LoginFormBloc(),
child: MaterialApp(
home: LoginFormScreen(),
),
),
);
}
class LoginFormScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Login')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: BlocListener<LoginFormBloc, LoginFormState>(
listener: (context, state) {
if (state.status == Status.submissionInProgress) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Submitting...')),
);
} else if (state.status == Status.submissionSuccess) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Login Successful')),
);
} else if (state.status == Status.submissionFailure) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Login Failed')),
);
}
},
child: FlutterFormBloc<LoginFormBloc, LoginFormState, LoginFormFields>(
onSubmit: (bloc) => bloc.add(Submitted()),
child: Column(
children: <Widget>[
TextFormFieldBlocBuilder<LoginFormBloc, LoginFormState, String>(
formBloc: (context) => context.read<LoginFormBloc>(),
field: LoginFormFields.email,
builder: (context, field, state) {
return TextFormField(
decoration: InputDecoration(labelText: 'Email'),
onChanged: (value) {
context.read<LoginFormBloc>().add(EmailChanged(email: value));
},
validator: (value) {
return !field.isValid ? 'Invalid email' : null;
},
);
},
),
TextFormFieldBlocBuilder<LoginFormBloc, LoginFormState, String>(
formBloc: (context) => context.read<LoginFormBloc>(),
field: LoginFormFields.password,
builder: (context, field, state) {
return TextFormField(
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
onChanged: (value) {
context.read<LoginFormBloc>().add(PasswordChanged(password: value));
},
validator: (value) {
return !field.isValid ? 'Password must be at least 6 characters' : null;
},
);
},
),
SizedBox(height: 24),
ElevatedButton(
onPressed: () {
context.read<LoginFormBloc>().add(Submitted());
},
child: Text('Login'),
),
],
),
),
),
),
);
}
}
总结
以上代码展示了如何使用 flutter_form_bloc
来管理一个简单的登录表单。通过分离逻辑和 UI 层,代码更加清晰和模块化,方便维护和扩展。希望这个示例对你有所帮助!