You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 01:52:03