I am trying to integrate flutter_login widget with bloc. Here is the sample code I am using
BlocProvider(
create: (ctx) => UserAuthenticationPageBloc(ctx),
child: BlocListener<UserAuthenticationPageBloc, UserAuthenticationPageState>(
listener: (context, state) {
// not used at the moment
},
child: BlocBuilder<UserAuthenticationPageBloc, UserAuthenticationPageState>(
builder: (context, state) {
final bloc = context.read<UserAuthenticationPageBloc>();
return FlutterLogin(onLogin: (loginData) async {
bloc.add(SignIn(loginData: loginData));
return state.loginMessage;
}, onRecoverPassword: (email) async {
bloc.add(RecoverPassword(email: email));
return state.recoverPasswordMessage;
});
},
),
),
)
Here is the bloc file
class UserAuthenticationPageBloc extends Bloc<UserAuthenticationPageEvent, UserAuthenticationPageState> {
UserAuthenticationPageBloc(BuildContext context) : super(const UserAuthenticationPageState()) {
on<SignIn>((event, emit) {
try {
emit(state.copyWith(signInStatus: SignInStatus.loading));
User user = User(); // need to be replaced by async http call
final GlobalBloc globalBloc = BlocProvider.of<GlobalBloc>(context);
globalBloc.add(GlobalSignIn(user: user));
emit(state.copyWith(loginMessage: 'some error', signInStatus: SignInStatus.failure));
} catch (_) {
//emit(CovidError("Failed to fetch data. is your device online?"));
}
});
on<RecoverPassword>((event, emit) {
});
}
}
What I would like to do is to add an event to bloc and then return a message. The flutter_login
widget will show snack bar based on the message returned.
How can I wait for bloc event to finish before retrieving the loginMessage from the state? Or maybe I should not put the loginMessage in state?
Thank you
In most scenarios, these types of things can all be handled pretty easily with the BlocListener
widget. Usually, in the UI you don't have to manually await
anything coming from a bloc stream. This is one exception, where you're trying to utilize the built in UI elements of flutter_widget
.
Since the onLogin
callback requires a return of null
on success or error message string on failure, you want to await
the stream directly.
The syntax for that looks like this
final authBloc = context.read<GlobalBloc>();
// fire the sign in event
// You know the first emitted state will be loading, so the next state is either success or failure
await authBloc.stream.firstWhere(
(state) => state.status != AuthStatus.loading,
);
The whole widget would look something like this.
FlutterLogin(
onLogin: (loginData) async {
final authBloc = context.read<GlobalBloc>()
..add(
SignIn(
email: loginData.name,
password: loginData.password,
),
);
await authBloc.stream.firstWhere(
(state) => state.status != AuthStatus.loading,
);
return authBloc.state.status == AuthStatus.authenticated
? null
: 'Login failed';
},
onRecoverPassword: (_) async {
return null;
},
);
A couple things with what you shared.
BlocBuilder
widgets should not be placed directly below the BlocProvider
. So in this case, to avoid that you could just create a new widget with the BlocBuilder
wrapping a FlutterLogin
. Although as you can see, in this case to use the built in snackbar, you don't need the BlocBuilder
here.
UserAuthenticationPageBloc
depends directly on GlobalBloc
, which according to the docs should be avoided at all times. This is easily avoided with the BlocListener
widget. I'm also not seeing why there are 2 separate blocs here at all just for login. So my example was simplified to just one Bloc class that handles login.The gif below shows a 2 second wait and the bloc emitting an error state.
To answer your question in the comment:
I get that you want the auth info to be globally accessible, that's pretty common. Typically I would have my AuthBloc
at the top of the widget tree along with any other Bloc that needs to be globally accessible. Not sure sure if you have other stuff going on in your GlobalBloc
besides login, if not, just I'd just rename that to something like AuthBloc
and get rid the separate one just for the login page.
If you do have other stuff going on in your GlobalBloc
, I'd still just create an AuthBloc
at the top of the widget tree and use that to manage the state of your login page, and that bloc would only be responsible for authentication. Either way, accessing blocs directly from other blocs makes testing much harder and is easily avoidable by utilizing the BlocListener
widget. That is at least true when initially setting up blocs and general architecture. If you ignore all of that and when things get complex and you have lots of direct dependencies going on, it gets much harder to separate.
I would also avoid passing in BuildContext
to a Bloc class. Even if you do need another Bloc, I would pass in the Bloc object directly (but again, don't do that). A bloc depending on BuildContext
will also make testing harder.
About network calls in the build method, after re-reading your code this is my mistake. I see that you're just passing in callback to the onLogin
param of the FlutterLogin
widget, not actually making a network request in the build method, sorry for the confusion there.
Feel free to share your GlobalBloc
if you're still looking for refactor help.