Search code examples
flutteronchangebloc

Flutter Bloc onChange and State Not Working


I'm working on flutter bloc to make login status for entire app

but I'm having some problems

The functions I wrote are working properly but when I try to access variables in main function by eg'(bloc.state.loginstate)' it is showing unupdated results.

also onChange is not showing any results on debug console too.

enter image description here

main Function

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);
    }
  }  
}

I checked props copyWith Equatable but is not working


Solution

  • The issue is that you're using two different instances of LoginDataBloc.

    You create one instance using the BlocProvider:

    BlocProvider(create: (BuildContext context) => LoginDataBloc(), lazy: false)
                                                // ^^^^^^^^^^^^^^ creates an instance
    

    Then, inside _YourinAppState, you create a different instance:

    final bloc = LoginDataBloc(); // this is not the same one as above
    

    Since every instance has its own state, you are modifying the first instance, and then checking the state of the second instance. This will clearly not reflect any operations from the first instance.

    Solution: Remove final bloc = LoginDataBloc();, and simply access your bloc's state with context.read<LoginDataBloc>().state.


    Why is onChange not being called?

    Answer: You're declaring the onChange method override inside the constructor, instead of inside the class definition (see comments).

    class LoginDataBloc extends Bloc<LoginDataEvent, LoginDataState> {
      LoginDataBloc() : super(LoginDataState()) {
        /* ... event handlers ... */
    
        /// Oops, this is actually just a function declaration inside the
        /// constructor, it's not overriding anything.
        @override // NOT WORKING
        void onChange(Change<LoginDataState> change) {
          super.onChange(change);
          print(change);
        }
      }
    }
    

    Solution: Move the onChange definition inside the class definition (see comments).

    class LoginDataBloc extends Bloc<LoginDataEvent, LoginDataState> {
      LoginDataBloc() : super(LoginDataState()) {
        /* ... event handlers ... */
      }
    
      /// Now this is overriding the Bloc's onChange method.
      @override // WORKING
      void onChange(Change<LoginDataState> change) {
        super.onChange(change);
        print(change);
      }
    }