Search code examples
flutterbloccubit

Cubit is not listening to Bloc


I'm working on a Flutter app following the BLoC pattern with clean architecture and using dependency injection for service management. I have an that handles user authentication and emits a to indicate whether the user is logged in or not. I also have a that is supposed to listen to the . When the user is authenticated, the should trigger a function to fetch user info.

Problem Description

The issue I'm facing is that the is not listening to the correctly. Specifically, the listener inside the constructor doesn't seem to be receiving the stream events from , leading to null values being displayed on the home screen when trying to access user data.

UserCubit

class UserCubit extends Cubit<UserState> {
  final AuthBloc _authBloc;
  final FetchUserinfoUsecase fetchUserinfoUsecase;
  late final StreamSubscription _authBlocSubscription;

  UserCubit(this._authBloc, this.fetchUserinfoUsecase)
      : super(const UserState.unknown()) {
    monitorAuthBloc();
  }

  void monitorAuthBloc() {
    _authBlocSubscription = _authBloc.stream.listen((authState) {
      log('AuthBloc state: $authState');

      if (authState.result == AuthResult.authenticated) {
        fetchUserInfo();
      }
    }, onDone: () {
      log('AuthBloc done');
    }, onError: (error) {
      log('AuthBloc error: $error');
    });
  }

  void fetchUserInfo() async {
    emit(state.copyWith(isLoading: true));
    final result = await fetchUserinfoUsecase();

    result.fold(
      (failure) => emit(state.copyWith(
        isLoading: false,
        updatedUser: null,
        updatedErrorMessage: failure.message,
      )),
      (instructor) => emit(
        state.copyWith(
          isLoading: false,
          updatedUser: instructor,
        ),
      ),
    );
  }

  @override
  Future<void> close() {
    _authBlocSubscription.cancel();
    return super.close();
  }
}

AuthBLoC

class AuthBloc extends Bloc<AuthEvent, AuthState> {
  final InternetCubit internetCubit;
  late final StreamSubscription internetStreamSubscription;
  final LoginUsecase loginUsecase;
  final LogoutUsecase logoutUsecase;

  AuthBloc({
    required this.internetCubit,
    required this.loginUsecase,
    required this.logoutUsecase,
  }) : super(const AuthState.unknown()) {
    internetStreamSubscription = internetCubit.stream.listen((internetState) {
      log(internetState.toString());
    });
    on<LoginButtonPressed>(_onLoginButtonPressed);
    on<LogoutButtonPressed>(_onLogoutButtonPressed);
  }

  bool get isAlreadyLoggedIn => loginUsecase.isAlreadyLoggedIn;
  UserId? get userId => loginUsecase.userId;
  String? get email => loginUsecase.email;

  void _onLoginButtonPressed(
    LoginButtonPressed event,
    Emitter<AuthState> emit,
  ) async {
    emit(state.copyWith(isLoading: true));
    final result = await loginUsecase(
      LoginParams(
        email: event.email,
        password: event.password,
      ),
    );

    result.fold(
      (failure) => emit(state.copyWith(
        isLoading: false,
        updatedResult: AuthResult.unauthenticated,
        updatedErrorMessage: failure.errorMessage,
      )),
      (success) => emit(state.copyWith(
        isLoading: false,
        updatedResult: AuthResult.authenticated,
      )),
    );
  }

  void _onLogoutButtonPressed(
    LogoutButtonPressed event,
    Emitter<AuthState> emit,
  ) async {
    emit(state.copyWith(isLoading: true));
    final result = await logoutUsecase();
    result.fold(
      (failure) => emit(state.copyWith(
        isLoading: false,
        updatedErrorMessage: failure.errorMessage,
      )),
      (success) => emit(state.copyWith(
        isLoading: false,
        updatedResult: AuthResult.unauthenticated,
      )),
    );
  }

  @override
  Future<void> close() {
    internetStreamSubscription.cancel();
    return super.close();
  }
}

Dependency Injection Setup

di.registerLazySingleton(() => InternetCubit(
      internetConnection: di.serviceLocator.call(),
    ))
  ..registerLazySingleton(() => AuthBloc(
      internetCubit: di.serviceLocator.call(),
      loginUsecase: di.serviceLocator.call(),
      logoutUsecase: di.serviceLocator.call(),
    ))
  ..registerFactory(() => UserCubit(
      di.serviceLocator.call(),
      di.serviceLocator.call(),
    ));

Main setup

MultiBlocProvider(
  providers: [
    BlocProvider(
      create: (_) => di.serviceLocator<InternetCubit>(),
      lazy: false,
    ),
    BlocProvider(
      create: (_) => di.serviceLocator<AuthBloc>(),
      lazy: false,
    ),
    BlocProvider(create: (_) => di.serviceLocator<UserCubit>()),
  ],
)

Issue

The UserCubit does not seem to be listening to the AuthBloc stream correctly. Specifically, the listener inside [tag:UserCubit's] constructor doesn't seem to respond to stream events from , leading to null values being displayed on the home screen when user data should be available.

  • Checked Stream Subscription: I confirmed that the UserCubit is correctly subscribing to the AuthBloc stream by adding logging statements inside the monitorAuthBloc method and ensuring that the listener is being called.
  • Verified State Emissions: I verified that the AuthBloc is correctly emitting states by adding logging statements in the _onLoginButtonPressed and _onLogoutButtonPressed methods to check if the authenticated state is being emitted.
  • Dependency Injection Configuration: I reviewed the dependency injection setup to ensure that AuthBloc and UserCubit are being created and provided correctly.
  • Initialization Order: I checked the initialization order in the MultiBlocProvider to make sure that AuthBloc and UserCubit are initialized in the correct sequence.

What I Expected

I expected that when the AuthBloc emits an authenticated state, the UserCubit would receive this event and trigger the fetchUserInfo method. Consequently, user data should be fetched and displayed correctly on the home screen.

Questions

  1. Are there any issues with the way UserCubit listens to AuthBloc's stream?
  2. Could the problem be related to the order of initialization or timing of state emissions?
  3. How can I ensure that UserCubit correctly listens to AuthBloc and responds to changes?

Solution

  • It look's like this is issue with different user cubit instances. Try to initialize UserCubit not as factory but as singlton. Do you really need this:

      ..registerFactory(() => UserCubit(
            di.serviceLocator.call(),
            di.serviceLocator.call(),
          ));
    

    instead of:

      ..registerSingleton(UserCubit(
            di.serviceLocator.call(),
            di.serviceLocator.call(),
          ))
    

    ?

    Also, is it some kind of clever trick to instantioate cubit with registerLazySingleton and then set lazy: false in BlocProvider?