DEV Community

Cover image for ๐Ÿš€ How I Simplified Flutter State Management with BLoC
Mohd Samir
Mohd Samir

Posted on

๐Ÿš€ How I Simplified Flutter State Management with BLoC

โ€œBLoC is powerful โ€” but managing loading, success, error, and empty states across different features can quickly become repetitive. I wanted something cleaner. Hereโ€™s the pattern I built and now rely on in all my Flutter projects.โ€

## ๐ŸŽฏ The Problem
If youโ€™ve worked with Flutter BLoC for more than a week, youโ€™ve probably ended up with a BLoC state that looks something like this:

bool isLoading;
bool hasError;
String errorMessage;
List<Comment> comments;
Enter fullscreen mode Exit fullscreen mode

Or worse, you created multiple state subclasses:

class CommentInitial extends CommentState {}
class CommentLoading extends CommentState {}
class CommentSuccess extends CommentState {
  final List<Comment> comments;
}
class CommentError extends CommentState {
  final String error;
}
Enter fullscreen mode Exit fullscreen mode

This works โ€” but it gets bloated fast, especially when your app scales and you need this for every feature.

## ๐Ÿงช The Solution: RequestStatus
Instead of defining multiple flags or subclasses, I created a generic state wrapper to manage idle, loading, success, error, and empty states in a single, reusable class.

enum RequestState { idle, loading, success, error, empty }

class RequestStatus<T> {
  final RequestState state;
  final T? data;
  final String? error;

  const RequestStatus._(this.state, {this.data, this.error});

  const RequestStatus.idle() : this._(RequestState.idle);
  const RequestStatus.loading() : this._(RequestState.loading);
  const RequestStatus.success({T? data}) : this._(RequestState.success, data: data);
  const RequestStatus.error(String error) : this._(RequestState.error, error: error);
  const RequestStatus.empty() : this._(RequestState.empty);

  bool get isLoading => state == RequestState.loading;
  bool get isSuccess => state == RequestState.success;
  bool get isError => state == RequestState.error;
  bool get isIdle => state == RequestState.idle;
  bool get isEmpty => state == RequestState.empty;
}
Enter fullscreen mode Exit fullscreen mode

**

๐Ÿงฑ How I Use It in BLoC

**
My BLoC state now looks like this:

class CommentState extends Equatable {
  final RequestStatus<List<Comment>> commentsStatus;

  const CommentState({required this.commentsStatus});

  factory CommentState.initial() => CommentState(
        commentsStatus: RequestStatus.idle(),
      );

  CommentState copyWith({RequestStatus<List<Comment>>? commentsStatus}) {
    return CommentState(
      commentsStatus: commentsStatus ?? this.commentsStatus,
    );
  }

  @override
  List<Object?> get props => [commentsStatus];
}
Enter fullscreen mode Exit fullscreen mode

The event handler becomes super clean:

on<FetchComments>((event, emit) async {
  emit(state.copyWith(commentsStatus: RequestStatus.loading()));
  try {
    final comments = await repository.getComments();
    emit(state.copyWith(
      commentsStatus: comments.isEmpty
          ? RequestStatus.empty()
          : RequestStatus.success(data: comments),
    ));
  } catch (e) {
    emit(state.copyWith(commentsStatus: RequestStatus.error(e.toString())));
  }
});
Enter fullscreen mode Exit fullscreen mode

## ๐Ÿงฉ The UI Logic is a Dream

final status = context.watch<CommentBloc>().state.commentsStatus;

if (status.isLoading) {
  return CircularProgressIndicator();
} else if (status.isError) {
  return Text('Error: ${status.error}');
} else if (status.isEmpty) {
  return Text('No comments found.');
} else if (status.isSuccess) {
  return CommentList(comments: status.data!);
} else {
  return SizedBox.shrink(); // idle
}
Enter fullscreen mode Exit fullscreen mode

No more flags, no more switch statements, and no more CommentLoadingState vs. CommentLoadedState spaghetti.

โœ… Why This Works So Well

๐Ÿ” Reusable: Works across features (comments, users, posts, messages, etc.)
๐Ÿงน Cleaner: Replaces tons of boilerplate code
๐ŸŽฏ Focused: UI reacts to a single object with readable getters
๐Ÿ“ฆ Scalable: Easy to plug into any feature with async operations

Conclusion

This tiny abstraction made a huge difference in how I structure and manage state in my Flutter apps. Itโ€™s flexible, readable, and keeps both my BLoCs and UIs lightweight.

๐Ÿ’ฌ What Do You Think?

Have you built something similar? Do you see areas where this pattern could be improved further? Iโ€™d love to hear your thoughts and ideas โ€” drop them in the comments or connect with me on:

LinkedIn

Top comments (0)