--- 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` 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` → `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 ```dart class CounterCubit extends Cubit { CounterCubit() : super(0); void increment() => emit(state + 1); void decrement() => emit(state - 1); } ``` ### Bloc Example ```dart sealed class CounterEvent extends Equatable { const CounterEvent(); @override List get props => []; } final class CounterIncrementPressed extends CounterEvent {} final class CounterDecrementPressed extends CounterEvent {} class CounterBloc extends Bloc { CounterBloc() : super(0) { on((event, emit) => emit(state + 1)); on((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 | ```dart sealed class TodoListEvent extends Equatable { const TodoListEvent(); @override List get props => []; } final class TodoListSubscriptionRequested extends TodoListEvent {} final class const TodoListTodoDeleted({required final Todo todo}) extends TodoListEvent { @override List 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 | ```dart sealed class LoginState extends Equatable { const LoginState(); @override List get props => []; } final class LoginInitial extends LoginState {} final class LoginInProgress extends LoginState {} final class const LoginSuccess({required final User user}) extends LoginState { @override List get props => [user]; } final class const LoginFailure({required final String error}) extends LoginState { @override List get props => [error]; } ``` #### Single Class Approach (one state, multiple fields) Use when all states share the same data shape: one class holding a `status` enum plus the data fields, with a `copyWith` for transitions. See [references/patterns.md](references/patterns.md) for the full shape. --- ## Architecture | Layer | Contains | Depends on | | ------------------ | ---------------------------- | ---------------- | | **Presentation** | Pages, Views, Widgets | Business Logic | | **Business Logic** | Blocs, Cubits | Data | | **Data** | Repositories, Data Providers | External sources | ### Data Layer 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](references/architecture.md) for the repository example, feature folder structure, and test directory layout. --- ## Flutter Widgets Use `BlocProvider` to supply a Bloc or Cubit to a subtree, then `BlocBuilder`, `BlocListener`, `BlocConsumer`, or `BlocSelector` to consume it. Reach for `BlocSelector` when a rebuild should depend on one field rather than the whole state, and `BlocListener` for side effects such as navigation or a snackbar. The rule that matters: **`context.read` in callbacks** (`onPressed`, `onTap`), **`context.watch` or `BlocBuilder` in `build`**. Never call `context.watch` outside a `build` method. ## Additional Resources - [references/architecture.md](references/architecture.md) — repository example, feature folder structure, test directory layout - [references/widgets.md](references/widgets.md) — widget and context extension tables, Page/View pattern, `BlocListener` example - [references/testing.md](references/testing.md) — `blocTest()` parameters, Cubit/Bloc test examples, mocking dependencies, widget integration tests - [references/patterns.md](references/patterns.md) — single-class state, adding features with Bloc/Cubit, async operations, event transformers