Skip to content

feat: Optionally support current state in BlocListener #4347

@zbarbuto

Description

@zbarbuto

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.

Metadata

Metadata

Assignees

Labels

enhancement candidateCandidate for enhancement but additional research is neededpkg:flutter_blocThis issue is related to the flutter_bloc package

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions