Skip to content
Mobile
Skill

/bloc

Best practices for Bloc state management in Flutter/Dart, covering Cubit versus Bloc, event and state naming, sealed classes with Equatable, the Page/View split with BlocProvider, BlocBuilder, BlocListener, and BlocSelector. Use when writing, modifying, or reviewing code that

BOOST
From plugin
vgv-ai-flutter-plugin
17315 skills1 agent3 hooks2 MCP
Install
$ npx -y skills add VeryGoodOpenSource/vgv-ai-flutter-plugin --skill bloc --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/bloc

Context preview

The summary Claude sees to decide when to auto-load this skill.

Best practices for Bloc state management in Flutter/Dart, covering Cubit versus Bloc, event and state naming, sealed classes with Equatable, the Page/View split with BlocProvider, BlocBuilder, BlocListener, and BlocSelector. Use when writing, modifying, or reviewing code that

SKILL.md

bloc.SKILL.md
name: bloc
description: >
  Best practices for Bloc state management in Flutter/Dart, covering Cubit versus Bloc, event and
  state naming, sealed classes with Equatable, the Page/View split with BlocProvider, BlocBuilder,
  BlocListener, and BlocSelector. Use when writing, modifying, or reviewing code that uses
  package:bloc, package:flutter_bloc, or package:bloc_test. Also use for any test of a class that
  extends Bloc or Cubit, including "write the unit tests for this bloc", "test my cubit", "add
  tests for LoginBloc", mocking a repository a bloc depends on, and asserting the states an event
  emits. Bloc and Cubit tests must use blocTest() from package:bloc_test with package:mocktail, so
  prefer this over the testing skill whenever the class under test is a Bloc or Cubit. Also use
  when a request pushes against these rules, such as injecting one bloc into another to read its
  state, calling a repository from a widget or build method, or keeping a Cubit for a feature that
  needs an event transformer such as debounce.
allowed-tools: Read Glob Grep

Bloc

State management library for Dart and Flutter using the BLoC (Business Logic Component) pattern to separate business logic from the presentation layer.

---

Core Standards

Apply these standards to all Bloc/Cubit work:

  • **Use `blocTest()` from `package:bloc_test`** for all Bloc and Cubit tests — never raw `test()` with manual stream assertions
  • **Use `package:mocktail` for mocking** — never `package:mockito`
  • **`build:` constructs the bloc under test** — return a new instance from the callback, never one built in `setUp` and shared, so every `blocTest` starts from a clean bloc and its lifecycle stays with `blocTest`
  • **No bloc-to-bloc direct dependencies** — blocs communicate through the UI or shared repositories
  • **Page/View separation** — Page provides the Bloc/Cubit via `BlocProvider`, View consumes via `BlocBuilder`/`BlocListener`
  • **Sealed classes for events and multi-state types** — enables exhaustive pattern matching with Dart 3 `switch`
  • **Equatable for all states and events** — extend `Equatable` and override `props` for value equality
  • **Business logic in Bloc/Cubit only** — never in widgets, pages, or views
  • **Single responsibility** — one Bloc/Cubit per feature concern
  • **`emit` only inside the handler** — call `emit` from the `on<Event>` callback or Cubit method that owns it, never from a stream subscription or callback that outlives the handler
  • **Dart 3.13 primary constructors** — on a Dart 3.13+ baseline, declare event, state, and widget fields as primary-constructor declaring parameters (`class const ProfileRequested(final String userId) extends ProfileEvent`) rather than `this.field`; keep the classic form only below 3.13

---

Cubit vs Bloc

| 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 |

Cubit Example

class CounterCubit extends Cubit<int> {
  CounterCubit() : super(0);

  void increment() => emit(state + 1);
  void decrement() => emit(state - 1);
}

Bloc Example

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));
  }
}

---

Naming Conventions

Events

**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 const TodoListTodoDeleted({required final Todo todo})
    extends TodoListEvent {
  @override
  List<Object> get props => [todo];
}

States

Subclass Approach (multiple state types)

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 const LoginSuccess({required final User user}) extends LoginState {
  @override
  List<Object> get props => [user];
}
final class const LoginFailure({required final String error})
    extends LoginState {
  @override
  List<Object> get props => [error];
}

Single Class Approach (one state, multiple fields)

Use when all state

Read more
Ships withvgv-ai-flutter-plugin

A Claude Code plugin that accelerates Flutter & Dart development with best-practices skills from Very Good Ventures. Developed with 💙 by Very Good Ventures 🦄

Get the whole plugin

Other skills on vgv-ai-flutter-plugin.