Flutter BLoC v6实现带验证登录表单及按钮激活问题求助
我完全懂你的困惑——自定义BLoC刚摸明白,转官方flutter_bloc库还赶上版本大更新,旧教程全失效,确实头大。我给你写一个**flutter_bloc v6+**的完整登录表单示例,正好解决你最关心的「只有两个字段都验证通过才激活提交按钮」的需求。
先理清核心逻辑
在flutter_bloc的范式里,我们需要:
- 事件(Event):描述用户的操作(输入邮箱、输入密码、点击登录)
- 状态(State):描述当前表单的状态(邮箱内容、密码内容、各自的错误、表单是否可提交)
- Bloc:接收事件,处理业务逻辑(验证规则),输出新状态
第一步:配置依赖
先在pubspec.yaml里添加最新的依赖(确保是v6+版本):
dependencies: flutter: sdk: flutter flutter_bloc: ^6.1.1 # 或更高稳定版 equatable: ^2.0.3 # 用于简化状态/事件的相等比较
第二步:定义事件(LoginEvent)
我们需要三个事件:邮箱输入变化、密码输入变化、提交登录。用Equatable让事件可以被Bloc正确识别和比较:
import 'package:equatable/equatable.dart'; abstract class LoginEvent extends Equatable { const LoginEvent(); @override List<Object> get props => []; } // 邮箱输入变化事件 class EmailChanged extends LoginEvent { final String email; const EmailChanged({required this.email}); @override List<Object> get props => [email]; } // 密码输入变化事件 class PasswordChanged extends LoginEvent { final String password; const PasswordChanged({required this.password}); @override List<Object> get props => [password]; } // 提交登录事件 class LoginSubmitted extends LoginEvent {}
第三步:定义状态(LoginState)
状态要包含表单的所有关键信息,这样UI能根据状态实时更新:
import 'package:equatable/equatable.dart'; abstract class LoginState extends Equatable { const LoginState(); @override List<Object> get props => []; } // 初始状态 class LoginInitial extends LoginState {} // 表单输入状态(核心状态) class LoginInputState extends LoginState { final String email; final String password; final String? emailError; // 邮箱错误信息,null代表无错误 final String? passwordError; // 密码错误信息,null代表无错误 final bool isFormValid; // 表单是否可提交(两个字段都验证通过) const LoginInputState({ required this.email, required this.password, this.emailError, this.passwordError, required this.isFormValid, }); @override List<Object> get props => [email, password, isFormValid]; // 方便复制状态,只修改需要更新的字段 LoginInputState copyWith({ String? email, String? password, String? emailError, String? passwordError, bool? isFormValid, }) { return LoginInputState( email: email ?? this.email, password: password ?? this.password, emailError: emailError ?? this.emailError, passwordError: passwordError ?? this.passwordError, isFormValid: isFormValid ?? this.isFormValid, ); } } // 登录加载状态(可选,比如点击登录后显示加载) class LoginLoading extends LoginState {} // 登录成功状态(可选) class LoginSuccess extends LoginState {}
第四步:实现LoginBloc
这部分是核心,处理事件、验证规则,输出新状态:
import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; // 导入上面定义的事件和状态 import 'login_events.dart'; import 'login_states.dart'; class LoginBloc extends Bloc<LoginEvent, LoginState> { LoginBloc() : super(LoginInitial()) { on<EmailChanged>(_onEmailChanged); on<PasswordChanged>(_onPasswordChanged); on<LoginSubmitted>(_onLoginSubmitted); } // 处理邮箱变化事件 void _onEmailChanged(EmailChanged event, Emitter<LoginState> emit) { // 获取当前状态,如果是初始状态就初始化空值 final currentState = state is LoginInputState ? state as LoginInputState : null; String email = event.email; String? emailError; // 邮箱验证规则:包含@符号 if (!email.contains('@')) { emailError = '请输入有效的邮箱地址'; } // 计算当前表单是否有效 bool isFormValid = emailError == null && (currentState?.passwordError == null); // 输出新状态 emit(LoginInputState( email: email, password: currentState?.password ?? '', emailError: emailError, passwordError: currentState?.passwordError, isFormValid: isFormValid, )); } // 处理密码变化事件 void _onPasswordChanged(PasswordChanged event, Emitter<LoginState> emit) { final currentState = state is LoginInputState ? state as LoginInputState : null; String password = event.password; String? passwordError; // 密码验证规则:长度大于4位 if (password.length <= 4) { passwordError = '密码长度必须大于4位'; } // 计算当前表单是否有效 bool isFormValid = passwordError == null && (currentState?.emailError == null); // 输出新状态 emit(LoginInputState( email: currentState?.email ?? '', password: password, emailError: currentState?.emailError, passwordError: passwordError, isFormValid: isFormValid, )); } // 处理登录提交事件 void _onLoginSubmitted(LoginSubmitted event, Emitter<LoginState> emit) async { // 先输出加载状态 emit(LoginLoading()); // 这里写实际的登录逻辑,比如调用API // await Future.delayed(Duration(seconds: 2)); // 模拟网络请求 // 登录成功后输出成功状态,或者失败状态(可以扩展) // emit(LoginSuccess()); // 示例里暂时回到输入状态 final currentState = state is LoginInputState ? state as LoginInputState : LoginInputState(email: '', password: '', isFormValid: false); emit(currentState); } }
第五步:UI层实现(对应你的LoginScreen)
用BlocProvider提供Bloc实例,用BlocBuilder监听状态变化,实时更新UI:
import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; // 导入上面的Bloc、事件、状态 import 'login_bloc.dart'; import 'login_events.dart'; import 'login_states.dart'; class LoginScreen extends StatelessWidget { const LoginScreen({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return BlocProvider( create: (context) => LoginBloc(), child: Scaffold( appBar: AppBar(title: const Text('登录')), body: Padding( padding: const EdgeInsets.all(16.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: const [ _EmailField(), SizedBox(height: 16), _PasswordField(), SizedBox(height: 24), _SubmitButton(), ], ), ), ), ); } } // 邮箱输入框 class _EmailField extends StatelessWidget { const _EmailField({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return BlocBuilder<LoginBloc, LoginState>( builder: (context, state) { String? errorText; if (state is LoginInputState) { errorText = state.emailError; } return TextField( keyboardType: TextInputType.emailAddress, decoration: InputDecoration( hintText: 'you@example.com', labelText: '邮箱地址', errorText: errorText, border: const OutlineInputBorder(), ), onChanged: (email) { context.read<LoginBloc>().add(EmailChanged(email: email)); }, ); }, ); } } // 密码输入框 class _PasswordField extends StatelessWidget { const _PasswordField({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return BlocBuilder<LoginBloc, LoginState>( builder: (context, state) { String? errorText; if (state is LoginInputState) { errorText = state.passwordError; } return TextField( obscureText: true, decoration: InputDecoration( labelText: '密码', errorText: errorText, border: const OutlineInputBorder(), ), onChanged: (password) { context.read<LoginBloc>().add(PasswordChanged(password: password)); }, ); }, ); } } // 提交按钮(核心:只有表单验证通过才激活) class _SubmitButton extends StatelessWidget { const _SubmitButton({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return BlocBuilder<LoginBloc, LoginState>( builder: (context, state) { bool isEnabled = false; // 如果是加载状态,按钮不可用 if (state is LoginLoading) { isEnabled = false; } // 如果是输入状态,根据isFormValid判断 else if (state is LoginInputState) { isEnabled = state.isFormValid; } return ElevatedButton( onPressed: isEnabled ? () { context.read<LoginBloc>().add(LoginSubmitted()); } : null, // 不可用时设置为null style: ElevatedButton.styleFrom( minimumSize: const Size(double.infinity, 48), backgroundColor: isEnabled ? Colors.blue : Colors.grey, ), child: state is LoginLoading ? const CircularProgressIndicator(color: Colors.white) : const Text('登录', style: TextStyle(color: Colors.white)), ); }, ); } }
关键细节解释
- 状态管理:用
LoginInputState统一管理所有表单状态,避免了自定义BLoC里多个Stream的混乱,所有状态变化都通过Bloc统一输出。 - 表单验证逻辑:每次输入变化时,都会重新计算
isFormValid字段(邮箱无错误 + 密码无错误),UI直接根据这个字段控制按钮是否可用。 - 版本适配:用了flutter_bloc v6+的
on<Event>语法替代旧版的mapEventToState,这是v6之后的新写法,也是现在的标准用法。
内容的提问来源于stack exchange,提问作者ConleeC
相关产品推荐
相关产品推荐

