State Management · · 6 min read
Ghost State in BLoC đź‘»
What is Ghost State?
Ghost state occurs when your BLoC maintains state in private fields that are never emitted as part of the state object. This creates a hidden source of truth that makes your code unpredictable and hard to debug.
A Real Example
Imagine you’re building a paginated list. You might be tempted to track the current page inside the Cubit like this:
// ❌ BAD: Ghost state
class PaginationCubit extends Cubit<PaginationState> {
int _currentPage = 1; // This is ghost state!
PaginationCubit() : super(const PaginationState(items: [], isLoading: false));
Future<void> nextPage() async {
_currentPage++;
final items = await repository.fetchItems(page: _currentPage);
emit(PaginationState(items: items, isLoading: false));
}
}
The _currentPage field is invisible to the outside world. You can’t log it, you can’t test it, and you can’t restore it. It’s a ghost.
The Fix
Move everything into the state object so it becomes the single source of truth:
// âś… GOOD: All state in the state object
class PaginationState {
final int currentPage;
final List<Item> items;
final bool isLoading;
const PaginationState({
required this.currentPage,
required this.items,
required this.isLoading,
});
PaginationState copyWith({
int? currentPage,
List<Item>? items,
bool? isLoading,
}) {
return PaginationState(
currentPage: currentPage ?? this.currentPage,
items: items ?? this.items,
isLoading: isLoading ?? this.isLoading,
);
}
}
class PaginationCubit extends Cubit<PaginationState> {
PaginationCubit()
: super(const PaginationState(
currentPage: 1,
items: [],
isLoading: false,
));
Future<void> nextPage() async {
final nextPage = state.currentPage + 1;
emit(state.copyWith(isLoading: true));
final items = await repository.fetchItems(page: nextPage);
emit(PaginationState(
currentPage: nextPage,
items: items,
isLoading: false,
));
}
}
Now every piece of information is visible, testable, and restorable.
Why It Matters
- Predictability — State changes are always visible
- Debugging — You can log and inspect all state changes
- Testing — State is the single source of truth
- Hydration — If you ever need to restore state (e.g. after a restart), everything is in one place
Conclusion
Keep all your state in the state object. No exceptions!