felangel / felangel/bloc

feat: Optionally support current state in `BlocListener`

Open
#4,347 3 comments 2 reactions 1 assignee Claimed by @felangel View on GitHub
enhancement candidate pkg:flutter_bloc
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.