Realtime Firestore Updates in Flutter: Switch from Cubit to Bloc
Firestore streams do not sit neatly inside a Cubit because Cubit has no built-in lifecycle for StreamSubscriptions. You end up exposing streams directly to the UI or leaking listeners.
Bloc solves this cleanly: events drive state, and you can own subscriptions inside the Bloc and cancel them in close().
Minimal pattern
Events and states:
sealed class OrdersEvent {}
class OrdersStarted extends OrdersEvent {}
class OrdersUpdated extends OrdersEvent {
final QuerySnapshot snapshot;
OrdersUpdated(this.snapshot);
}
sealed class OrdersState {}
class OrdersLoading extends OrdersState {}
class OrdersLoaded extends OrdersState {
final List<Map<String, dynamic>> orders;
OrdersLoaded(this.orders);
}
class OrdersError extends OrdersState {}
Bloc:
class OrdersBloc extends Bloc<OrdersEvent, OrdersState> {
final FirebaseFirestore firestore;
StreamSubscription<QuerySnapshot>? _ordersSub;
OrdersBloc(this.firestore) : super(OrdersLoading()) {
on<OrdersStarted>(_onStarted);
on<OrdersUpdated>(_onUpdated);
}
Future<void> _onStarted(OrdersStarted event, Emitter<OrdersState> emit) async {
await _ordersSub?.cancel();
_ordersSub = firestore
.collection('orders')
.snapshots()
.listen(
(snapshot) => add(OrdersUpdated(snapshot)),
onError: (_) => emit(OrdersError()),
);
}
void _onUpdated(OrdersUpdated event, Emitter<OrdersState> emit) {
final orders = event.snapshot.docs.map((d) => d.data()).toList();
emit(OrdersLoaded(orders));
}
@override
Future<void> close() {
_ordersSub?.cancel();
return super.close();
}
}
Key points:
- Keep the Firestore
StreamSubscriptionprivate to the Bloc. - Translate Firestore snapshots into Bloc events; emit derived state for the UI.
- Cancel the subscription in
close()to avoid leaks when the Bloc is disposed. - Start the stream by dispatching
OrdersStarted(e.g. frominitState) and surface errors as a state rather than crashing the UI.
If you are starting from Cubit, the swap is straightforward—see Matt Rešetár’s tutorial for the basics. The main win is lifecycle control for realtime streams without exposing them to the UI layer.