Flutter Bloc onChange无输出且State状态无法更新问题求助
Flutter Bloc登录状态管理问题排查与修复
我用Flutter Bloc实现全应用登录状态管理时遇到两个问题:
- 功能函数能正常运行,但通过
bloc.state.loginState访问变量时总是未更新的旧值 onChange方法在调试控制台没有任何输出
原代码片段
main函数
void main(){run(MyApp());} class MyApp extends StatelessWidget{ @override Widget build(BuildContext context){ return MultiBlocProvider( providers: [ BlocProvider(create: (BuildContext context) => LoginDataBloc(), lazy: false,), ], child: MaterialApp( ... ) ); } } class YoureInApp extends StatefulWidget { @override _YoureInAppState createState() => _YoureInAppState(); } class _YoureInAppState extends State<YoureInApp> { final bloc = LoginDataBloc(); @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { _autologin(); }); } _autologin () async { context.read<LoginDataBloc>().add(ReadSignEvent()); //copyWith Done, ReadSignEvent Done message Future.delayed(Duration(seconds: 2) ,(){ context.read<LoginDataBloc>().add(PrintSignEvent()); // loginState : true, {'accountName : ... '} print(bloc.state.loginState); // loginState : false - Problem if(bloc.state.loginState == false) Navigator.push(context, MaterialPageRoute(builder: (_) => LoginStartApp())); }); }
State类
class LoginDataState extends Equatable { final String userInfo; final bool loginState; LoginDataState({this.userInfo = '', this.loginState = false}); LoginDataState copyWith({String? userInfo, bool? loginState}) { return LoginDataState( userInfo: userInfo?? this.userInfo, loginState: loginState?? this.loginState, ); } @override List<Object?> get props => [userInfo, loginState]; }
Event类
abstract class LoginDataEvent extends Equatable{ } class SignInEvent extends LoginDataEvent { final String accountName; final String phoneNumber; final String passWord; SignInEvent(this.accountName, this.phoneNumber, this.passWord); @override List<Object?> get props => [accountName, phoneNumber, passWord]; } class SignOutEvent extends LoginDataEvent { @override List<Object?> get props => []; } class ReadSignEvent extends LoginDataEvent { @override List<Object?> get props => []; } class PrintSignEvent extends LoginDataEvent { @override List<Object?> get props => []; }
Bloc类
class LoginDataBloc extends Bloc<LoginDataEvent,LoginDataState>{ LoginDataBloc(): super(LoginDataState()){ const storage = FlutterSecureStorage(); on<SignInEvent> ((event, emit) async { String userInfo = jsonEncode(Login(event.accountName, event.phoneNumber, event.passWord)); await storage.write(key: 'login', value: userInfo); emit(state.copyWith(userInfo: userInfo)); emit(state.copyWith(loginState: true)); }); on<SignOutEvent>((event, emit) async { await storage.delete(key: 'login'); emit(state.copyWith(userInfo: '')); emit(state.copyWith(loginState: false)); }); on<ReadSignEvent>((event, emit) async { var userInfo = await storage.read(key: 'login'); if(userInfo != null) { emit(state.copyWith(loginState: true)); emit(state.copyWith(userInfo: userInfo)); print("copyWith Done"); // TEST FUNCTION } print('ReadSignEvent'); //TEST FUNCTION }); on<PrintSignEvent>((event, emit) { print(state.loginState); //TEST FUNCTION print(state.userInfo); //TEST FUNCTION },); @override // NOT WORKING void onChange(Change<LoginDataState> change){ super.onChange(change); print(change); } } }
问题修复方案
1. 解决Bloc实例不一致问题
你在_YoureInAppState里手动创建的bloc实例,和MultiBlocProvider提供的全局实例是两个完全独立的对象:发送事件用的是全局实例,但读取状态时用的是本地实例,自然拿不到更新后的值。
修改方式:删除本地bloc实例,统一通过context.read获取全局实例:
class _YoureInAppState extends State<YoureInApp> { // 移除本地bloc实例:final bloc = LoginDataBloc(); @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { _autologin(); }); } _autologin () async { final loginBloc = context.read<LoginDataBloc>(); loginBloc.add(ReadSignEvent()); Future.delayed(Duration(seconds: 2) ,(){ loginBloc.add(PrintSignEvent()); print(loginBloc.state.loginState); // 现在能获取到更新后的状态 if(loginBloc.state.loginState == false) Navigator.push(context, MaterialPageRoute(builder: (_) => LoginStartApp())); }); }
2. 修复onChange方法位置错误
你把onChange写在了Bloc构造函数的内部大括号里,导致方法未被正确重写。需要将其移到类的顶层,和on<Event>方法同级:
修改后的Bloc类:
class LoginDataBloc extends Bloc<LoginDataEvent,LoginDataState>{ const storage = FlutterSecureStorage(); // 移到构造函数外,避免重复创建 LoginDataBloc(): super(LoginDataState()){ on<SignInEvent> ((event, emit) async { String userInfo = jsonEncode(Login(event.accountName, event.phoneNumber, event.passWord)); await storage.write(key: 'login', value: userInfo); // 合并两次emit,减少不必要的状态更新 emit(state.copyWith(userInfo: userInfo, loginState: true)); }); on<SignOutEvent>((event, emit) async { await storage.delete(key: 'login'); // 合并emit操作 emit(state.copyWith(userInfo: '', loginState: false)); }); on<ReadSignEvent>((event, emit) async { var userInfo = await storage.read(key: 'login'); if(userInfo != null) { // 合并两次emit为一次 emit(state.copyWith(loginState: true, userInfo: userInfo)); print("copyWith Done"); } print('ReadSignEvent'); }); on<PrintSignEvent>((event, emit) { print(state.loginState); print(state.userInfo); }); } // 正确重写onChange方法 @override void onChange(Change<LoginDataState> change){ super.onChange(change); print(change); } }
3. 优化连续emit的写法
原代码中多次连续调用emit(state.copyWith(...)),每次emit都会触发状态更新,合并成一次copyWith可以同时修改多个字段,既高效又符合Bloc最佳实践。
4. 替换Future.delayed为可靠的状态监听
用Future.delayed等待状态更新不可靠(操作耗时可能超过设定时长),建议通过监听Bloc的stream来判断状态变化:
_autologin () async { final loginBloc = context.read<LoginDataBloc>(); loginBloc.add(ReadSignEvent()); try { // 等待loginState变为true,超时时间5秒 await loginBloc.stream.firstWhere( (state) => state.loginState == true, orElse: () => LoginDataState(), ); loginBloc.add(PrintSignEvent()); } on TimeoutException catch (_) { // 超时则跳转到登录页 Navigator.push(context, MaterialPageRoute(builder: (_) => LoginStartApp())); } }
内容的提问来源于stack exchange,提问作者code_San
相关产品推荐
相关产品推荐

