-
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Description
Description
Given BlocListener
is intended to react to changes in state, the current default makes sense: the listener is not called immediately with the current state (as documented).
However, I've come across a few instances where it would be useful to have the option for a listener run once with the current state in addition to future transitions. Generally, instances where you want some feedback in the UI based on state which isn't applicable in a builder
or using context.select
- like snackbars or route changes.
Alternatives Considered
The current workaround for this is to make sure the widget is stateful and include an initState()
. For example, let's say we have a page that is only visible to logged-in users. We want it to re-route users that are not currently logged in in addition to users who log out in future.
class AuthenticatedPage extends StatefulWidget {
const AuthenticatedPage({super.key});
@override
State<AuthenticatedPage> createState() => _AuthenticatedPageState();
}
class _AuthenticatedPageState extends State<AuthenticatedPage> {
@override
void initState() {
super.initState();
// Handle the current bloc state
if (!context.read<AuthBloc>().state.isLoggedIn) {
_signout();
}
}
@override
Widget build(BuildContext context) {
return BlocListener<AuthBloc, AuthState>(
// Handle changes to bloc state
listenWhen: (previous, next) => previous.isLoggedIn && !next.isLoggedIn,
listener: (context, state) {
_signout();
},
child: MyAuthenticatedPageContent(),
);
}
_signout() {
context.router.replace(const UnauthenticatedRoute());
}
}
Alternatively, we use something like StreamBuilder
with a parameter like
stream: context.read<MyCubit>().stream.startWith(context.read<MyCubit>().state);
But this feels way hackier.
Desired Solution
It would clean up a few things to have some sort of optional flag (like startWithCurrentState
but probably a better name) that will immediately call the listener once with the current state. With this, we could simplify our widget down to be stateless:
class AuthenticatedPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocListener<AuthBloc, AuthState>(
// Ensure the listener is checked immediately on creation with current state in addition to future transitions
startWithCurrentState: true,
listenWhen: (previous, next) => previous.isLoggedIn && !next.isLoggedIn,
listener: (context, state) {
if(!state.isLoggedIn) {
context.router.replace(const UnauthenticatedRoute());
}
},
child: MyAuthenticatedPageContent(),
);
}
}
If it makes more sense this could be its own widget rather than a flag on BlocListener
.