feat: Optionally support current state in `BlocListener`
- Dominant language
- Dart
- Stars
- 12.5k
- Forks
- 3.4k
- PR merge metrics
- No merged PRs in 30d
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](https://bloclibrary.dev/flutter-bloc-concepts/#bloclistener)).
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.
```dart
class AuthenticatedPage extends StatefulWidget {
const AuthenticatedPage({super.key});
@override
State createState() => _AuthenticatedPageState();
}
class _AuthenticatedPageState extends State {
@override
void initState() {
super.initState();
// Handle the current bloc state
if (!context.read().state.isLoggedIn) {
_signout();
}
}
@override
Widget build(BuildContext context) {
return BlocListener(
// 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().stream.startWith(context.read().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:
```dart
class AuthenticatedPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocListener(
// 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`.
Contributor guide
Assessment
This issue has not been assessed yet.