I try to solve that problem a couple of days, but I can't. I have a login form, when user signs in, the LoginBloc changes its state to LogIn() and BlocListener(which is located in HomeView.dart) must change HomeBloc's state from Initial() to Loaded(), but it doesn't work. I don't know why but BlocListener ignores that LoginBloc has LogIn() state and doesn't run HomeBlocEvent.loadUserDetails() I appreciate any help or pieces of advice how to solve this problem. I used Firebase as Authentication service.
// LoginBloc.dart
@injectable
class LoginBloc extends Bloc<LoginEvent, LoginState> {
final UserSignInImpl _userSignInImpl;
LoginBloc(this._userSignInImpl) : super(const LoginState.initial()) {
on<SignIn>((event, emit) async {
emit(const LoginState.loading());
final result = await _userSignInImpl.call(event.user);
result.fold((error) {
emit(LoginState.loadFailed(error));
}, (success) {
emit(LoginState.logIn(user: success));
});
});
}
}
// HomeView.dart
@override
Widget build(BuildContext context) {
final homeBloc = BlocProvider.of<HomeBloc>(context);
final loginBloc = BlocProvider.of<LoginBloc>(context);
......
body: BlocListener<LoginBloc, LoginState>(
listener: (context, state) {
state.maybeWhen(
// function below isn't called, but LoginState is LogIn()
logIn: (user) => homeBloc.add(
HomeEvent.loadUserDetails(user.email!),
),
orElse: () {},
);
},
child: NestedScrollView(
headerSliverBuilder: (context, bool innerBoxIsScrolled) => [
const HomeSliverAppBar(),
],
body: BlocBuilder<HomeBloc, HomeState>(
bloc: homeBloc,
builder: (context, state) {
return state.maybeWhen(
loaded: (userInfo) => const HomeListView(),
// TODO: implement shimmer view
orElse: () => const Center(child: CircularProgressIndicator()),
);
},
),
),
),
// HomeBloc.dart
@injectable
class HomeBloc extends Bloc<HomeEvent, HomeState> {
HomeBloc() : super(const HomeState.initial()) {
on<LoadUserDetails>((event, emit) async {
emit(const HomeState.loading());
await fbFireStore.getUserDetails(event.email).then(
(userDetails) => emit(HomeState.loaded(user: userDetails)),
);
});
on<SignOut>((event, emit) async {
await fbAuth.signOut();
});
}
}
// LoginView.dart
@override
Widget build(BuildContext context) {
final loginBloc = BlocProvider.of<LoginBloc>(context);
.....
child: ElevatedButton(
style: AppStyles.widePurpleButtonStyle,
onPressed: () {
if (_signInKey.currentState!.validate()) {
// Adding SignIn() event
loginBloc.add(LoginEvent.signIn(LoginUser(
email: _emailController.text.trim(),
password: _passwordController.text.trim(),
)));
}
},
child: Text(S.of(context).logIn),
),
.....
SizedBox(
height: MediaQuery.sizeOf(context).height * 0.05,
child: BlocConsumer<LoginBloc, LoginState>(
builder: (context, state) {
return state.maybeWhen(
loading: () {
return const CircularProgressIndicator();
},
orElse: () {
return const SizedBox();
},
);
},
listener: (context, state) {
state.maybeWhen(
loadFailed: (failure) {
showDialog(
context: context,
builder: (context) {
return ErrorAlertDialog(error: failure);
},
);
},
logIn: (_) {
context.router.navigate(const HomeRoute());
},
orElse: () {},
);
},
),
),
BlocListener must run HomeEvent.loadUserDetails() when LoginBloc's state is LogIn()
The thing is that when LoginBloc
emits LoginState.logIn()
your HomeView
is not yet in the Navigation stack. Note that you are performing context.router.navigate(const HomeRoute());
inside listener of BlocConsumer<LoginBloc, LoginState>()
in LoginView
.
So when HomeView
is actually opened LoginBloc
state is LoginState.logIn()
already and no other states are emitted by LoginBloc
and that's why your listener in HomeView
is not receiving anything.
To access user details inside HomeView
you can just get LoginBloc
state:
final state = loginBloc.state; // LoginState.login(user: ...)
But I suggest just to pass a user email
as a parameter for HomeView
and then use it to load user details.