Flutter BLoC: BlocBuilder, BlocListener & BlocConsumer
📘 Introduction

In Flutter app development, effective state management is crucial. The BLoC (Business Logic Component) pattern offers a reactive, stream-based way to manage state by separating business logic from the UI.
Key Widgets in the BLoC Pattern:
-
BlocBuilder– Rebuilds parts of the UI in response to new states emitted by a BLoC or Cubit. It’s used when you want the UI to reactively update based on state changes. -
BlocListener– Listens to state changes and performs side effects such as showing snackbars, dialogs, or navigating to different screens. It does not rebuild the UI. -
BlocConsumer– CombinesBlocBuilderandBlocListener. It lets you both rebuild the UI and perform side effects in a single widget, useful when both are needed in the same context.
MultiBlocProvider – Managing Multiple BLoCs
When your app uses multiple BLoCs or Cubits, wrapping each one in its own BlocProvider becomes messy. Instead, use MultiBlocProvider, which groups multiple BlocProvider widgets together in a clean and scalable way.
This tutorial walks you through examples for each widget: a counter, an auth flow, and a shopping cart.

📂 Project Structure & Code
lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'cubits/counter_cubit.dart';
import 'blocs/auth_bloc.dart';
import 'blocs/cart_bloc.dart';
import 'screens/home_page.dart';
void main() {
runApp(
MultiBlocProvider(
providers: [
BlocProvider(create: (_) => CounterCubit()),
BlocProvider(create: (_) => AuthBloc()),
BlocProvider(create: (_) => CartBloc()),
],
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter BLoC Demo',
home: HomePage(),
routes: {
'/home': (_) => HomePage(),
},
);
}
}
lib/cubits/counter_cubit.dart
import 'package:flutter_bloc/flutter_bloc.dart';
class CounterCubit extends Cubit {
CounterCubit() : super(0);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
}
lib/blocs/auth_bloc.dart
import 'package:flutter_bloc/flutter_bloc.dart';
abstract class AuthState {}
class AuthInitial extends AuthState {}
class AuthSuccess extends AuthState {}
class AuthFailure extends AuthState {}
class AuthBloc extends Cubit {
AuthBloc() : super(AuthInitial());
void login(String username, String password) {
if (username == 'admin' && password == '123') {
emit(AuthSuccess());
} else {
emit(AuthFailure());
}
}
}
lib/blocs/cart_bloc.dart
import 'package:flutter_bloc/flutter_bloc.dart';
// --- States ---
abstract class CartState {}
class CartLoading extends CartState {}
class CartLoaded extends CartState {
final List items;
CartLoaded(this.items);
}
class CheckoutSuccess extends CartState {}
// --- BLoC ---
class CartBloc extends Cubit {
List _items = [];
CartBloc() : super(CartLoading()) {
loadCart();
}
void loadCart() async {
await Future.delayed(Duration(seconds: 1));
_items = ['Item 1', 'Item 2'];
emit(CartLoaded(List.from(_items)));
}
void addItem(String item) {
_items.add(item);
emit(CartLoaded(List.from(_items)));
}
void checkout() {
_items.clear();
emit(CheckoutSuccess());
}
}
lib/screens/home_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../cubits/counter_cubit.dart';
import '../blocs/auth_bloc.dart';
import '../blocs/cart_bloc.dart';
import 'login_form.dart';
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('BLoC Demo')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: SingleChildScrollView(
// Added in case the content overflows
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('BlocBuilder (Counter):'),
BlocBuilder<CounterCubit, int>(
builder: (context, count) {
return Text('Count: $count', style: TextStyle(fontSize: 24));
},
),
Row(
children: [
ElevatedButton(
onPressed: () => context.read().increment(),
child: Text('+'),
),
SizedBox(width: 8),
ElevatedButton(
onPressed: () => context.read().decrement(),
child: Text('-'),
),
],
),
SizedBox(height: 24),
Text('BlocListener (Auth):'),
BlocListener<AuthBloc, AuthState>(
listener: (context, state) {
if (state is AuthSuccess) {
Navigator.pushNamed(context, '/home');
} else if (state is AuthFailure) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Login failed')),
);
}
},
child: LoginForm(),
),
SizedBox(height: 24),
Text('BlocConsumer (Cart):'),
BlocConsumer<CartBloc, CartState>(
listener: (context, state) {
if (state is CheckoutSuccess) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Order placed!')),
);
}
},
builder: (context, state) {
if (state is CartLoading) {
return CircularProgressIndicator();
} else if (state is CartLoaded) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Items: ${state.items.length}'),
SizedBox(height: 8),
ElevatedButton(
onPressed: () {
final newItem = 'Item ${state.items.length + 1}';
context.read().addItem(newItem);
},
child: Text('Add Item'),
),
SizedBox(height: 8),
ElevatedButton(
onPressed: () => context.read().checkout(),
child: Text('Checkout'),
),
],
);
} else {
return Text('Cart is empty');
}
},
),
],
),
),
),
);
}
}
lib/screens/login_form.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../blocs/auth_bloc.dart';
class LoginForm extends StatelessWidget {
final TextEditingController _usernameController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
@override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
controller: _usernameController,
decoration: InputDecoration(labelText: 'Username'),
),
TextField(
controller: _passwordController,
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
),
ElevatedButton(
onPressed: () {
context.read().login(
_usernameController.text,
_passwordController.text,
);
},
child: Text('Login'),
),
],
);
}
}
🧠 Summary
BlocBuilder is used for rebuilding the UI based on new states. BlocListener is useful for handling side effects without triggering rebuilds. BlocConsumer gives you both capabilities — it’s a combination of builder and listener.
