There are several valid patterns for Flutter BloC to BloC Comminication. Let’s break them down and summarize the pros and cons of each, plus a few extra notes to guide best practice.
✅ 1. Bloc 1 injected as a property inside Bloc 2
How it works:
BlocB receives an instance of BlocA, listens to its stream, and reacts accordingly.
class BlocB extends Bloc<BlocBEvent, BlocBState> {
final BlocA blocA;
late final StreamSubscription subscription;
BlocB(this.blocA) {
subscription = blocA.stream.listen((state) {
if (state is BlocASomeState) add(BlocBEvent());
});
}
@override
Future<void> close() {
subscription.cancel();
return super.close();
}
}
✅ Pros:
- Direct and straightforward.
- Easy to maintain with few blocs.
❌ Cons:
- Tight coupling between blocs.
- Harder to test and scale in large systems.
✅ 2. Bloc 2 listens via BlocListener in the UI and passes events to Bloc 2
How it works:
UI uses BlocListener<BlocA> to observe BlocA‘s state and then manually dispatches an event to BlocB.
BlocListener<BlocA, BlocAState>(
listener: (context, state) {
if (state is BlocASomeState) {
context.read<BlocB>().add(BlocBEvent());
}
},
child: YourWidget(),
)
✅ Pros:
- Decouples the blocs.
- UI mediates the interaction (good for UI-driven logic).
❌ Cons:
- UI gets bloated with logic.
- Hard to reuse in non-UI contexts.
✅ 3. Global Event Bus (e.g. StreamController-based)
How it works:
You define a singleton StreamController<Event> that all blocs can publish to and listen from.
final globalEventBus = StreamController<dynamic>.broadcast();
✅ Pros:
- Complete decoupling between blocs.
- Can act as a PubSub pattern across features.
❌ Cons:
- No type safety (unless you wrap events well).
- Harder to trace/debug.
- Easy to misuse — often turns into “god object” anti-pattern.
✅ 4. Global Event Bloc (acts as a shared mediator)
How it works:
You create a GlobalEventBloc that holds global events and states. Other blocs can listen to it or dispatch to it.
class GlobalEventBloc extends Bloc<GlobalEvent, GlobalState> {
// Dispatch global events here
}
✅ Pros:
- Centralized, testable, and predictable.
- All communication stays in the BLoC ecosystem.
- Good for cross-cutting concerns (auth, session, notifications).
❌ Cons:
- Introduces a global dependency.
- Risk of bloat if not scoped properly.
🔄 Comparison Summary:
| Method | Coupling | Testability | UI Independence | Scale Suitability |
|---|---|---|---|---|
| Bloc Injected | Medium | High | Yes | Small/Medium |
| UI via BlocListener | Low | MEDIUM | No | Small/Medium |
| Global Event Bus | Low | LOW | Yes | LARGE (with discipline) |
| Global Event Bloc | Low-Medium | High | Yes | LARGE |
🔔 Bonus: Other related patterns
- Cubit Delegation: One Cubit calls another Cubit’s method.
- Repository Mediation: Both blocs depend on a shared repo or service which acts as the sync layer.
- BlocObserver: Good for side effects like analytics/logging, not for logic transfer.
