Search code examples
androidiosflutterdartbloc

flutter cubit to cubit communication not working


NOTE: POST UPDATED!

I have two cubits one of them is LoginCubit which is responsible for login actions and has 3 different states( LoginLoading, LoginSuccess, LoginFailure). The other cubit is AuthCubit which has 4 states(AuthInitial, AuthLoading, Authenticated, Unauthenticated) and it is responsible for authentication events.

If the user is logged in (that means if the state is LoginSuccess), then the AuthState must be converted to Authenticated). At this point, I listen to LoginState from my AuthCubit, if any state changes occur. This is the code where AuthCubit listens LoginState.

AuthCubit

  final IsSignInUseCase isSignInUseCase;
  final SignOutUseCase signOutUseCase;
  final ReadAuthFromStorageUseCase readAuthFromStorageUseCase;
  final LoginCubit loginCubit;
  late final StreamSubscription streamSubscription;

  AuthCubit(
      {required this.signOutUseCase,
      required this.loginCubit,
      required this.readAuthFromStorageUseCase,
      required this.isSignInUseCase})
      : super(AuthInitial()) {
    streamSubscription = loginCubit.stream.listen((state) {

//here I listen to Login changes, if the login state is success, then authenticate

      if (state is LoginSuccess) {
        emit(Authenticated(authResponse: state.authResponse));
      }
    });
  }
  Future<void> checkAuthentication() async {
    emit(AuthLoading());
    bool isSignIn = await isSignInUseCase.call();

    if (isSignIn) {
      final AuthResponse authResponse = await readAuthFromStorageUseCase.call();
      emit(Authenticated(authResponse: authResponse));
    } else {
      emit(Unauthenticated());
    }
  }


//close stream sub
  @override
  Future<void> close() {
    streamSubscription.cancel();
    return super.close();
  }

Since I am listening to LoginState, now I can distinguish between the logged-in users and non-logged-in users. According to this, I build my screen as the following.

main.dart

BlocBuilder<AuthCubit, AuthState>(
          builder: (context, state) {
            if (state is Authenticated) {
              return const HomeScreen();
            } else if (state is Unauthenticated) {
              return BlocListener<LoginCubit, LoginState>(
                listener: (context, state) {
                  if (state is LoginFailure) {
                    showDialog(
                        context: context,
                        builder: (context) => CustomAlertDialog(
                            title: ExceptionConst.exceptionTitle,
                            content: state.appException.message,
                            onPressed: () => Navigator.pop(context)));
                  }
                },
                child: const LoginScreen(),
              );
            } else {
              return const SplashScreen();
            }
          },
        ));

I use get_it for di.

dependency_injection

 sl.registerFactory(() => LoginCubit(loginUserUseCase: sl.call()));
 sl.registerFactory(() => AuthCubit(
      isSignInUseCase: sl.call(),
      loginCubit: sl.call(),
      readAuthFromStorageUseCase: sl.call(),signOutUseCase: sl.call()));

  sl.registerLazySingleton(() => LoginUserUseCase(authRepo: sl.call()));
  sl.registerLazySingleton(() => SignUpUserUseCase(authRepo: sl.call()));
  sl.registerLazySingleton(() => IsSignInUseCase(authRepo: sl.call()));
  sl.registerLazySingleton(() => SignOutUseCase(authRepo: sl.call()));
  sl.registerLazySingleton(
      () => ReadAuthFromStorageUseCase(authRepo: sl.call()));
...

and lastly this is my MultiBlocProvider

void main() async {
  await di.init();
  runApp(MultiBlocProvider(providers: [
    BlocProvider(create: (_) => di.sl<LoginCubit>()),
    BlocProvider(create: (_) => di.sl<AuthCubit>()..checkAuthentication()),
  ], child: const MyApp()));
}

However, when I press the login button the AuthState is not changing unless I reload the app manually using hot restart.


Solution

  • I solved this problem by changing my listening technique from StreamSubscription to BlocListener. Still, I don't know why the StreamSubscription has not worked. Until someone can solve it I will accept my answer.

    Here is the solution, I cleaned all `StreamSubscription' codes.

    AuthCubit

    
    class AuthCubit extends Cubit<AuthState> {
      final IsSignInUseCase isSignInUseCase;
      final SignOutUseCase signOutUseCase;
      final ReadAuthFromStorageUseCase readAuthFromStorageUseCase;
    
      AuthCubit(
          {required this.signOutUseCase,
          required this.readAuthFromStorageUseCase,
          required this.isSignInUseCase})
          : super(AuthInitial());
      Future<void> checkAuthentication() async {
        emit(AuthLoading());
       try{
         bool isSignIn = await isSignInUseCase.call();
         if (isSignIn) {
           final AuthResponse authResponse = await readAuthFromStorageUseCase.call();
           emit(Authenticated(authResponse: authResponse));
         } else {
           emit(Unauthenticated());
         }
       }catch(_){
         emit(Unauthenticated());
       }
      }
    
      Future<void> signOut() async {
        try {
          emit(AuthLoading());
          await signOutUseCase.call();
          emit(Unauthenticated());
        } catch (e) {
          emit(Unauthenticated());
        }
      }
    }
    
    

    main.dart

    BlocBuilder<AuthCubit, AuthState>(
                builder: (context, authState) {
                  if (authState is Authenticated) {
                    return const HomeScreen();
                  } else if (authState is Unauthenticated) {
                    return BlocListener<LoginCubit, LoginState>(
                      
                      listener: (context, loginState) {
    //the following if check make it work. If the LoginSuccess then recheck authentication status.
                        if (loginState is LoginSuccess) {
                          BlocProvider.of<AuthCubit>(context).checkAuthentication();
                        }
                        if (loginState is LoginFailure) {
                          showDialog(
                              context: context,
                              builder: (context) => CustomAlertDialog(
                                  title: ExceptionConst.exceptionTitle,
                                  content: loginState.appException.message,
                                  onPressed: () => Navigator.pop(context)));
                        }
                      },
                      child: const LoginScreen(),
                    );
                  } else if (authState is AuthLoading) {
                    return const Center(
                      child: ButtonSpinner(),
                    );
                  }
                  return const SplashScreen();
                },
              ),
    
    

    finally in my LoginCubit I emit AuthState.

    class LoginCubit extends Cubit<LoginState> {
      final LoginUserUseCase loginUserUseCase;
      final AuthCubit authCubit;
    
      LoginCubit({required this.authCubit, required this.loginUserUseCase})
          : super(LoginInitial());
    
      Future<void> loginUser(LoginRequest loginDto) async {
        try {
          emit(LoginLoading());
          final authResponse = await loginUserUseCase.call(loginDto);
          emit(LoginSuccess(authResponse: authResponse));
    //if the login process is succeed, then change emit the AuthState as Authenticated.
          authCubit.emit(Authenticated(authResponse: authResponse));
        } on AppException catch (appEx) {
          emit(LoginFailure(appException: appEx));
        }
      }
    }