flutter
/

BlocListener: Handling Side Effects in Flutter Bloc

Last Sync: Today

On this page

11
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutter

BlocListener: Handling Side Effects in Flutter Bloc

In the BLoC pattern, not all state changes should directly modify the UI. Some changes require side effects – one‑time actions like showing a snackbar, navigating to another screen, or playing a sound. BlocListener is the dedicated widget for handling these side effects without causing unnecessary rebuilds.

What is BlocListener?

BlocListener is a Flutter widget that listens to state changes in a bloc and executes a callback in response. Unlike BlocBuilder, it does not rebuild its child. It is designed exclusively for side effects: actions that should happen once when a particular state is reached.

Basic Usage

Wrap a widget with BlocListener and provide a listener function. The listener is called whenever the state changes. You can optionally filter which states trigger the listener using the listenWhen parameter.

DARTRead-only
1
BlocListener<AuthBloc, AuthState>(
  listener: (context, state) {
    if (state is AuthSuccess) {
      Navigator.pushReplacementNamed(context, '/home');
    }
    if (state is AuthFailure) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(state.message)),
      );
    }
  },
  child: LoginForm(),
)

BlocListener vs BlocBuilder

FeatureBlocListenerBlocBuilder
Primary purposeSide effects (navigation, dialogs)UI rebuild on state change
Rebuilds childNoYes
Called on every stateYes (unless filtered)Yes (unless filtered)
Common use caseSnackbars, navigation, loggingDisplaying data, toggling UI

When to Use BlocListener

  • Navigation – Push a new screen when authentication succeeds.
  • Snackbars / Toasts – Show feedback messages on error or success.
  • Dialogs – Present a dialog when a specific state is reached.
  • Analytics / Logging – Send analytics events when certain states occur.
  • Focus management – Move focus to a text field after validation.

Filtering with listenWhen

By default, BlocListener calls the listener on every state change. To avoid unnecessary calls, use the listenWhen predicate. It receives the previous and current states and returns true only when you want the listener to run.

DARTRead-only
1
BlocListener<LoginBloc, LoginState>(
  listenWhen: (previous, current) {
    // Only trigger on success or failure, not loading
    return current is LoginSuccess || current is LoginFailure;
  },
  listener: (context, state) {
    if (state is LoginSuccess) {
      Navigator.pushReplacementNamed(context, '/dashboard');
    }
    if (state is LoginFailure) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(state.error)),
      );
    }
  },
  child: LoginForm(),
)

Multiple Listeners

You can use multiple BlocListener widgets for the same bloc to separate concerns. For example, one listener for navigation and another for snackbars. This keeps your code clean and focused.

DARTRead-only
1
BlocListener<AuthBloc, AuthState>(
  listenWhen: (_, current) => current is AuthSuccess,
  listener: (context, state) => Navigator.pushReplacementNamed(context, '/home'),
  child: BlocListener<AuthBloc, AuthState>(
    listenWhen: (_, current) => current is AuthFailure,
    listener: (context, state) => ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text((state as AuthFailure).message)),
    ),
    child: LoginForm(),
  ),
)

BlocListener with MultiBlocProvider

When you have multiple blocs, you can nest BlocListener widgets or combine them with MultiBlocProvider. A common pattern is to wrap your UI with a MultiBlocProvider and then a BlocListener for each relevant bloc.

DARTRead-only
1
MultiBlocProvider(
  providers: [
    BlocProvider<AuthBloc>(create: (_) => AuthBloc()),
    BlocProvider<SettingsBloc>(create: (_) => SettingsBloc()),
  ],
  child: BlocListener<AuthBloc, AuthState>(
    listener: (context, state) {
      if (state is AuthSuccess) {
        Navigator.pushReplacementNamed(context, '/home');
      }
    },
    child: MyApp(),
  ),
)

Testing with BlocListener

In widget tests, you can verify that the listener callback is triggered. Use tester.pumpWidget and then assert that navigation or other side effects occurred.

DARTRead-only
1
testWidgets('navigates on login success', (tester) async {
  final bloc = MockAuthBloc();
  whenListen(bloc, Stream.value(AuthSuccess()));

  await tester.pumpWidget(
    MaterialApp(
      home: BlocProvider.value(
        value: bloc,
        child: BlocListener<AuthBloc, AuthState>(
          listener: (context, state) {
            if (state is AuthSuccess) {
              Navigator.pushReplacementNamed(context, '/home');
            }
          },
          child: Container(),
        ),
      ),
    ),
  );

  expect(find.byType(Container), findsOneWidget);
  // In a real test, you would verify navigation occurred
});

Best Practices

  • Use listenWhen to avoid unnecessary calls – Prevents side effects from running on every state change.
  • Keep listener callbacks simple – Avoid heavy computation; delegate complex logic to the bloc if needed.
  • Separate concerns – Use multiple BlocListener widgets for different side effects.
  • Prefer BlocListener over manual checks in BlocBuilder – Avoid mixing UI rebuild logic with side effects.
  • Use BlocConsumer if you need both – BlocConsumer combines BlocListener and BlocBuilder in one widget.
  • Don't trigger side effects from inside BlocBuilder – That would run on every rebuild, leading to unexpected behaviour.

Common Mistakes

  • ❌ Using BlocBuilder for side effects – Leads to multiple executions if the widget rebuilds for other reasons.
  • ❌ Not filtering with listenWhen – The listener runs on every state change, including loading states, causing unintended actions.
  • ❌ Calling setState inside listener – The listener is called outside the build phase; use context to trigger navigation or show dialogs instead.
  • ❌ Misplacing BlocListener – If placed too deep, the context may not have access to the required bloc or navigator.
  • ❌ Assuming listener runs only once per state – If you navigate and the widget is rebuilt, the listener may run again; use listenWhen to guard against that.

What's Next?

Now that you understand side effects, explore how to combine builder and listener with BlocConsumer, and learn about advanced state management patterns.

Next, explore BlocConsumer and Bloc architecture.

Test Your Knowledge

Q1
of 3

What is the primary purpose of BlocListener?

A
Rebuild the UI when state changes
B
Handle side effects like navigation and snackbars
C
Combine multiple blocs into one
D
Provide a bloc to the widget tree
Q2
of 3

Which parameter allows you to conditionally trigger the listener based on previous and current states?

A
condition
B
filter
C
listenWhen
D
buildWhen
Q3
of 3

Does BlocListener rebuild its child widget?

A
Yes, on every state change
B
No, never
C
Only when listenWhen returns true
D
Yes, but only for initial state

Frequently Asked Questions

Can I use BlocListener without a child?

No, BlocListener always requires a child. If you need a listener that doesn't wrap existing UI, you can use a SizedBox.shrink() as the child. However, it's often better to place the listener at an appropriate location that wraps your UI.

What is the difference between BlocListener and BlocConsumer?

BlocConsumer combines BlocListener and BlocBuilder into one widget. It accepts both a listener (for side effects) and a builder (for UI). Use it when you need both side effects and UI rebuilds from the same bloc.

Does BlocListener rebuild the child when the state changes?

No. BlocListener does not rebuild its child. It only executes the listener callback. If you need to rebuild the UI, use BlocBuilder or BlocConsumer.

Can I use multiple BlocListeners for the same bloc?

Yes, it's a good practice to separate concerns. For example, one listener for navigation and another for showing dialogs. This keeps each callback focused and easier to test.

Previous

bloc builder

Next

bloc consumer

Related Content

Need help?

Explore our comprehensive docs or start a chat with our tech experts.