Enforces best practices for Bloc/Cubit state management in Flutter/Dart, including bloc_test/mocktail testing, sealed events/states, Equatable, page/view separation, and naming conventions. Use for writing, modifying, or reviewing bloc code.
npx claudepluginhub verygoodopensource/very_good_claude_code_marketplace --plugin vgv-ai-flutter-pluginThis skill is limited to using the following tools:
State management library for Dart and Flutter using the BLoC (Business Logic Component) pattern to separate business logic from the presentation layer.
Provides expert Flutter/Dart patterns for cross-platform mobile apps including feature-first project structure, const widget best practices, and Riverpod/Bloc state management.
Production-ready Dart and Flutter patterns covering null safety, immutable state, async composition, widget architecture, popular state management frameworks (BLoC, Riverpod, Provider), GoRouter navigation, Dio networking, Freezed code generation, and clean architecture.
Builds cross-platform Flutter 3+ apps with Dart: widget development, Riverpod/Bloc state management, GoRouter navigation, platform-specific implementations, performance optimization.
Share bugs, ideas, or general feedback.
State management library for Dart and Flutter using the BLoC (Business Logic Component) pattern to separate business logic from the presentation layer.
Apply these standards to ALL Bloc/Cubit work:
blocTest() from package:bloc_test for all Bloc and Cubit tests — never raw test() with manual stream assertionspackage:mocktail for mocking — never package:mockitoBlocProvider, View consumes via BlocBuilder/BlocListenerswitchEquatable and override props for value equalityemit only inside the handler callback| Aspect | Cubit | Bloc |
|---|---|---|
| API | Functions → emit(state) | Events → on<Event> → emit(state) |
| Complexity | Low | Higher |
| Traceability | Less (no event log) | Full (events + transitions) |
| When to use | Simple state, UI-driven logic | Complex flows, event-driven, transforms |
| Testing | Call methods, assert states | Add events, assert states |
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
}
sealed class CounterEvent extends Equatable {
const CounterEvent();
@override
List<Object> get props => [];
}
final class CounterIncrementPressed extends CounterEvent {}
final class CounterDecrementPressed extends CounterEvent {}
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<CounterIncrementPressed>((event, emit) => emit(state + 1));
on<CounterDecrementPressed>((event, emit) => emit(state - 1));
}
}
Pattern: BlocSubject + Noun + VerbPastTense
| Event class name | Meaning |
|---|---|
TodoListSubscriptionRequested | Subscribing to todo list stream |
TodoListTodoDeleted | Deleting a specific todo |
TodoListUndoDeletionRequested | Undoing the last deletion |
LoginFormSubmitted | Submitting the login form |
ProfilePageRefreshed | Refreshing the profile page |
sealed class TodoListEvent extends Equatable {
const TodoListEvent();
@override
List<Object> get props => [];
}
final class TodoListSubscriptionRequested extends TodoListEvent {}
final class TodoListTodoDeleted extends TodoListEvent {
const TodoListTodoDeleted({required this.todo});
final Todo todo;
@override
List<Object> get props => [todo];
}
Use when each state carries different data.
| State class name | Meaning |
|---|---|
LoginInitial | No action taken yet |
LoginInProgress | Login request in flight |
LoginSuccess | Login succeeded |
LoginFailure | Login failed |
sealed class LoginState extends Equatable {
const LoginState();
@override
List<Object> get props => [];
}
final class LoginInitial extends LoginState {}
final class LoginInProgress extends LoginState {}
final class LoginSuccess extends LoginState {
const LoginSuccess({required this.user});
final User user;
@override
List<Object> get props => [user];
}
final class LoginFailure extends LoginState {
const LoginFailure({required this.error});
final String error;
@override
List<Object> get props => [error];
}
Use when all states share the same data shape.
| Field | Type | Purpose |
|---|---|---|
status | enum | Current loading status |
items | List<Item> | Loaded data |
error | String? | Error message if failed |
enum TodoListStatus { initial, loading, success, failure }
class TodoListState extends Equatable {
const TodoListState({
this.status = TodoListStatus.initial,
this.todos = const [],
this.error,
});
final TodoListStatus status;
final List<Todo> todos;
final String? error;
TodoListState copyWith({
TodoListStatus? status,
List<Todo>? todos,
String? error,
}) {
return TodoListState(
status: status ?? this.status,
todos: todos ?? this.todos,
error: error ?? this.error,
);
}
@override
List<Object?> get props => [status, todos, error];
}
| Layer | Contains | Depends on |
|---|---|---|
| Presentation | Pages, Views, Widgets | Business Logic |
| Business Logic | Blocs, Cubits | Data |
| Data | Repositories, Data Providers | External sources |
Repositories abstract data sources and provide a clean API for Blocs/Cubits. Mirror the feature folder structure under test/ for all test files.
See references/architecture.md for the repository example, feature folder structure, and test directory layout.
BlocProvider — creates and provides a Bloc/Cubit to the subtreeBlocBuilder — rebuilds widget when state changesBlocListener — executes side effects (navigation, snackbar) on state changeBlocConsumer — combines BlocBuilder + BlocListenerBlocSelector — rebuilds only when a selected property changescontext.read in callbacks (onPressed, onTap), context.watch or BlocBuilder in build methodscontext.watch outside of buildSee references/widgets.md for the full widget and context extension tables, Page/View pattern example, and BlocListener example. See references/testing.md for blocTest() parameters, Cubit/Bloc test examples, mocking dependencies, and widget integration tests. See references/patterns.md for adding features with Bloc/Cubit, async operations, and event transformers.