I'm working on a Flutter app following the BLoC pattern with clean architecture and using dependency injection for service management. I have an authbloc that handles user authentication and emits a authresult to indicate whether the user is logged in or not. I also have a usercubit that is supposed to listen to the authbloc. When the user is authenticated, the usercubit should trigger a function to fetch user info.
The issue I'm facing is that the usercubit is not listening to the authbloc correctly. Specifically, the listener inside the usercubit constructor doesn't seem to be receiving the stream events from authbloc, leading to null values being displayed on the home screen when trying to access user data.
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();
}
}
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();
}
}
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(),
));
MultiBlocProvider(
providers: [
BlocProvider(
create: (_) => di.serviceLocator<InternetCubit>(),
lazy: false,
),
BlocProvider(
create: (_) => di.serviceLocator<AuthBloc>(),
lazy: false,
),
BlocProvider(create: (_) => di.serviceLocator<UserCubit>()),
],
)
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 authbloc, leading to null values being displayed on the home screen when user data should be available.
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.
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
?