Flutter Bloc
Predictable state management with business logic separation
🧩 What is Bloc?
Bloc (Business Logic Component) separates presentation from business logic using streams. It follows a unidirectional data flow pattern where events trigger state changes, making apps predictable, testable, and maintainable for complex applications.
// Event triggers state change
bloc.add(IncrementEvent());
Key Concepts
Events
Input to the Bloc
abstract class CounterEvent {}
class Increment extends CounterEvent {}
States
Output from the Bloc
class CounterState {
final int count;
}
Bloc
Converts events to states
class CounterBloc extends
Bloc<Event, State>
Streams
Reactive data flow
Stream<State> mapEventToState(
Event event)
🔹 Installation
Add Bloc packages to your pubspec.yaml:
dependencies:
flutter:
sdk: flutter
flutter_bloc: ^8.1.3
equatable: ^2.0.5 # For value comparison
Then run:
flutter pub get
🔹 Basic Counter Example
Create a counter using Bloc pattern:
import 'package:flutter_bloc/flutter_bloc.dart';
// 1. Define Events
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}
class DecrementEvent extends CounterEvent {}
// 2. Define State
class CounterState {
final int count;
CounterState(this.count);
}
// 3. Create Bloc
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(CounterState(0)) {
// Handle increment event
on<IncrementEvent>((event, emit) {
emit(CounterState(state.count + 1));
});
// Handle decrement event
on<DecrementEvent>((event, emit) {
emit(CounterState(state.count - 1));
});
}
}
Output:
A Bloc that handles increment and decrement events and emits new counter states.
🔹 Using Bloc in UI
Connect Bloc to your Flutter widgets:
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Provide Bloc to widget tree
return BlocProvider(
create: (context) => CounterBloc(),
child: MaterialApp(
home: CounterPage(),
),
);
}
}
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Bloc Counter')),
body: Center(
// Listen to state changes
child: BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
return Text(
'${state.count}',
style: TextStyle(fontSize: 48),
);
},
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: () {
// Add increment event
context.read<CounterBloc>().add(IncrementEvent());
},
child: Icon(Icons.add),
),
SizedBox(height: 10),
FloatingActionButton(
onPressed: () {
// Add decrement event
context.read<CounterBloc>().add(DecrementEvent());
},
child: Icon(Icons.remove),
),
],
),
);
}
}
Output:
A counter app with increment and decrement buttons using Bloc pattern.
🔹 BlocBuilder vs BlocListener
Different ways to react to state changes:
// BlocBuilder: Rebuilds UI on state change
BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
return Text('Count: ${state.count}');
},
)
// BlocListener: Performs side effects (navigation, snackbar, etc.)
BlocListener<CounterBloc, CounterState>(
listener: (context, state) {
if (state.count == 10) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Reached 10!')),
);
}
},
child: Container(),
)
// BlocConsumer: Combines both builder and listener
BlocConsumer<CounterBloc, CounterState>(
listener: (context, state) {
// Side effects
if (state.count < 0) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Negative!')),
);
}
},
builder: (context, state) {
// UI rebuild
return Text('${state.count}');
},
)
Output:
Different widgets for building UI, handling side effects, or both.
🔹 Todo List Example
A more practical Bloc example:
// Events
abstract class TodoEvent {}
class AddTodo extends TodoEvent {
final String title;
AddTodo(this.title);
}
class RemoveTodo extends TodoEvent {
final int index;
RemoveTodo(this.index);
}
// State
class TodoState {
final List<String> todos;
TodoState(this.todos);
}
// Bloc
class TodoBloc extends Bloc<TodoEvent, TodoState> {
TodoBloc() : super(TodoState([])) {
on<AddTodo>((event, emit) {
final updatedTodos = List<String>.from(state.todos)
..add(event.title);
emit(TodoState(updatedTodos));
});
on<RemoveTodo>((event, emit) {
final updatedTodos = List<String>.from(state.todos)
..removeAt(event.index);
emit(TodoState(updatedTodos));
});
}
}
// UI
class TodoPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Todo List')),
body: BlocBuilder<TodoBloc, TodoState>(
builder: (context, state) {
return ListView.builder(
itemCount: state.todos.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(state.todos[index]),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
context.read<TodoBloc>().add(RemoveTodo(index));
},
),
);
},
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
context.read<TodoBloc>().add(AddTodo('New Task'));
},
child: Icon(Icons.add),
),
);
}
}
Output:
A todo list app where you can add and remove tasks using Bloc.
🔹 Cubit: Simplified Bloc
Cubit is a simpler version of Bloc without events:
import 'package:flutter_bloc/flutter_bloc.dart';
// Cubit (no events needed)
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
}
// Usage in UI
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => CounterCubit(),
child: Scaffold(
body: Center(
child: BlocBuilder<CounterCubit, int>(
builder: (context, count) {
return Text('$count', style: TextStyle(fontSize: 48));
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
context.read<CounterCubit>().increment();
},
child: Icon(Icons.add),
),
),
);
}
}
Output:
A simpler counter using Cubit instead of full Bloc pattern.
🔹 When to Use Bloc
✅ Good For:
- Large, complex applications
- Apps with complex business logic
- When testability is critical
- Teams familiar with reactive programming
- Apps requiring clear separation of concerns
❌ Consider Alternatives For:
- Simple apps (use setState or Provider)
- Beginners learning Flutter
- Rapid prototyping
- When boilerplate is a concern
🔹 Bloc Best Practices
- Use Cubit for simple cases: Less boilerplate than full Bloc
- Use Equatable: For easy state comparison
- Keep Blocs focused: One Bloc per feature
- Test your Blocs: Easy to test without UI
- Use BlocObserver: For logging and debugging