How to Use BLoC in Flutter: Simple Guide with Example
To use
BLoC in Flutter, create a Bloc class that manages state and events, then provide it to your widget tree using BlocProvider. Use BlocBuilder to rebuild UI based on state changes. This pattern separates business logic from UI for cleaner, testable code.Syntax
The basic syntax involves creating a Bloc class that extends Bloc<Event, State>. You define events and states as classes. Use BlocProvider to make the bloc available to widgets, and BlocBuilder to rebuild UI when state changes.
- Bloc: Handles events and emits states.
- Event: User actions or triggers.
- State: UI representation at a moment.
- BlocProvider: Injects bloc into widget tree.
- BlocBuilder: Listens to bloc state and rebuilds UI.
dart
class CounterEvent {} class IncrementEvent extends CounterEvent {} class CounterState { final int count; CounterState(this.count); } class CounterBloc extends Bloc<CounterEvent, CounterState> { CounterBloc() : super(CounterState(0)) { on<IncrementEvent>((event, emit) => emit(CounterState(state.count + 1))); } } // Usage in widget tree: BlocProvider( create: (_) => CounterBloc(), child: BlocBuilder<CounterBloc, CounterState>( builder: (context, state) { return Text('Count: ${state.count}'); }, ), )
Example
This example shows a simple counter app using BLoC. Pressing the button sends an IncrementEvent to the bloc, which updates the state and rebuilds the UI with the new count.
dart
import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; // Events abstract class CounterEvent {} class IncrementEvent extends CounterEvent {} // State class CounterState { final int count; CounterState(this.count); } // Bloc class CounterBloc extends Bloc<CounterEvent, CounterState> { CounterBloc() : super(CounterState(0)) { on<IncrementEvent>((event, emit) => emit(CounterState(state.count + 1))); } } void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: BlocProvider( create: (_) => CounterBloc(), child: const CounterPage(), ), ); } } class CounterPage extends StatelessWidget { const CounterPage({super.key}); @override Widget build(BuildContext context) { final bloc = context.read<CounterBloc>(); return Scaffold( appBar: AppBar(title: const Text('BLoC Counter')), body: Center( child: BlocBuilder<CounterBloc, CounterState>( builder: (context, state) { return Text('Count: ${state.count}', style: const TextStyle(fontSize: 24)); }, ), ), floatingActionButton: FloatingActionButton( onPressed: () => bloc.add(IncrementEvent()), child: const Icon(Icons.add), ), ); } }
Output
A Flutter app with an AppBar titled 'BLoC Counter', a centered text showing 'Count: 0' initially, and a floating button with a plus icon. Pressing the button increments the count displayed.
Common Pitfalls
- Not disposing the bloc when no longer needed can cause memory leaks; use
BlocProviderto handle lifecycle automatically. - Mutating state directly instead of emitting new state objects breaks immutability and causes UI not to update.
- Forgetting to add events to the bloc means no state changes happen.
- Using
context.read<Bloc>()insidebuild()can cause issues; preferBlocBuilderorBlocListenerfor reacting to state.
dart
/* Wrong: Mutating state directly */ class CounterBloc extends Bloc<CounterEvent, CounterState> { CounterBloc() : super(CounterState(0)) { on<IncrementEvent>((event, emit) { // state.count += 1; // Wrong: state is immutable emit(CounterState(state.count + 1)); }); } } /* Right: Emit new state */ class CounterBloc extends Bloc<CounterEvent, CounterState> { CounterBloc() : super(CounterState(0)) { on<IncrementEvent>((event, emit) => emit(CounterState(state.count + 1))); } }
Quick Reference
- Bloc: Business logic component handling events and states.
- Event: Actions sent to bloc to trigger state changes.
- State: Immutable data representing UI at a time.
- BlocProvider: Injects bloc into widget tree and manages lifecycle.
- BlocBuilder: Rebuilds UI when bloc state changes.
- BlocListener: Reacts to state changes without rebuilding UI.
Key Takeaways
Use BlocProvider to inject and manage your bloc's lifecycle in the widget tree.
Define clear event and state classes to separate user actions from UI representation.
Emit new immutable states instead of mutating existing ones to update UI correctly.
Use BlocBuilder to rebuild UI based on bloc state changes.
Avoid accessing bloc directly inside build methods; prefer BlocBuilder or BlocListener.