Skip to content
Development
Skill

/bloc

Use when creating a Cubit or Bloc, modeling state with sealed classes or status enums, wiring BlocBuilder/BlocListener/BlocProvider, writing bloc tests, or choosing between Cubit and Bloc.

From plugin
flutter-ai-skills
63937 skills
Install
$ npx -y skills add evanca/flutter-ai-rules --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.

Use when creating a Cubit or Bloc, modeling state with sealed classes or status enums, wiring BlocBuilder/BlocListener/BlocProvider, writing bloc tests, or choosing between Cubit and Bloc.

SKILL.md

bloc.SKILL.md
name: bloc
description: "Use when creating a Cubit or Bloc, modeling state with sealed classes or status enums, wiring BlocBuilder/BlocListener/BlocProvider, writing bloc tests, or choosing between Cubit and Bloc."
license: MIT

Bloc Skill

Design, implement, and test state management using the [bloc](https://pub.dev/packages/bloc) and [flutter_bloc](https://pub.dev/packages/flutter_bloc) libraries.

When to Use

Use this skill when:

  • Creating a new Cubit or Bloc for a feature.
  • Modeling state (choosing between sealed classes and a single state class with status enum).
  • Wiring `BlocBuilder`, `BlocListener`, `BlocConsumer`, or `BlocProvider` in the widget tree.
  • Writing unit tests for a Cubit or Bloc.
  • Deciding between Cubit and Bloc.
  • Refactoring existing state management to follow bloc conventions.

---

1. Cubit vs Bloc

| Situation | Use | |---|---| | Simple state, no events needed | `Cubit` | | Complex flows, event traceability needed | `Bloc` | | Advanced event processing (debounce, throttle) | `Bloc` with event transformers |

**Default to `Cubit`. Refactor to `Bloc` only when requirements grow.**

---

2. Naming Conventions

Events (Bloc only)

  • Named in **past tense**: `LoginButtonPressed`, `UserProfileLoaded`.
  • Format: `BlocSubject` + optional noun + verb.
  • Initial load event: `BlocSubjectStarted` (e.g., `AuthenticationStarted`).
  • Base event class: `BlocSubjectEvent`.

States

  • Named as **nouns** (states are snapshots in time).
  • Base state class: `BlocSubjectState`.
  • Sealed subclasses: `BlocSubject` + `Initial` | `InProgress` | `Success` | `Failure`.
  • Example: `LoginInitial`, `LoginInProgress`, `LoginSuccess`, `LoginFailure`.
  • Single-class approach: `BlocSubjectState` + `BlocSubjectStatus` enum (`initial`, `loading`, `success`, `failure`).

---

3. Modeling State

When to use a sealed class with subclasses

  • States are **well-defined and mutually exclusive**.
  • Type-safe exhaustive `switch` is desired.
  • Subclass-specific properties exist.
@immutable
sealed class LoginState extends Equatable {
  const LoginState();
}

final class LoginInitial extends LoginState {
  @override
  List<Object?> get props => [];
}

final class LoginInProgress extends LoginState {
  @override
  List<Object?> get props => [];
}

final class LoginSuccess extends LoginState {
  const LoginSuccess(this.user);
  final User user;
  @override
  List<Object?> get props => [user];
}

final class LoginFailure extends LoginState {
  const LoginFailure(this.message);
  final String message;
  @override
  List<Object?> get props => [message];
}

Handle all states exhaustively in the UI:

switch (state) {
  case LoginInitial():  ...
  case LoginInProgress(): ...
  case LoginSuccess(:final user): ...
  case LoginFailure(:final message): ...
}

When to use a single class with a status enum

  • Many shared properties across states.
  • Simpler, more flexible; previous data must be retained after failure.
enum LoginStatus { initial, loading, success, failure }

@immutable
class LoginState extends Equatable {
  const LoginState({
    this.status = LoginStatus.initial,
    this.user,
    this.errorMessage,
  });

  final LoginStatus status;
  final User? user;
  final String? errorMessage;

  LoginState copyWith({
    LoginStatus? status,
    User? user,
    String? errorMessage,
  }) {
    return LoginState(
      status: status ?? this.status,
      user: user ?? this.user,
      errorMessage: errorMessage ?? this.errorMessage,
    );
  }

  @override
  List<Object?> get props => [status, user, errorMessage];
}

State rules (both approaches)

  • Extend `Equatable` and pass all relevant fields to `props`.
  • Copy `List`/`Map` properties with `List.of`/`Map.of` inside `props`.
  • Annotate with `@immutable`.
  • Always emit a **new instance**; never reuse the same state object.
  • Duplicate states are ignored by bloc — ensure meaningful state changes.

---

4. Cubit Implementation

class LoginCubit extends Cubit<LoginState> {
  LoginCubit(this._authRepository) : super(const LoginState());

  final AuthRepository _authRepository;

  Future<void> login(String email, String password) async {
    emit(state.copyWith(status: LoginStatus.loading));
    try {
      final user = await _authRepository.login(email, password);
      emit(state.copyWith(status: LoginStatus.success, user: user));
    } catch (e) {
      emit(state.copyWith(status: LoginStatus.failure, errorMessage: e.toString()));
    }
  }
}

Rules:

  • Only call `emit` inside the Cubit/Bloc.
  • Public methods return `void` or `Future<void>` only.
  • Keep business logic out of UI.
  • When overriding `storage` in a `HydratedCubit`, pass it as a named parameter: `super(initialState, storage: storage)`.

---

5. Bloc Implementation

sealed class LoginEvent {}
final class LoginSubmitted extends LoginEvent {
  LoginSubmitted({required this.email, required this.password});
  final String email;
  final String password;
}

class LoginBloc extends Bloc<LoginEvent, LoginState> {
  LoginBloc(this._authRepository) : super(LoginInitial()) {
    on<LoginSubmitted>(_onLoginSubmitted);
  }

  final AuthRepository _authRepository;

  Future<void> _onLoginSubmitted(
    LoginSubmitted event,
    Emitter<LoginState> emit,
  ) async {
    emit(LoginInProgress());
    try {
      final user = await _authRepository.login(event.email, event.password);
      emit(LoginSuccess(user));
    } catch (e) {
      emit(LoginFailure(e.toString()));
    }
  }
}

Rules:

  • Trigger state changes via `bloc.add(Event())`, not custom public methods.
  • Keep event handler methods private (`_onEventName`).
  • Internal/repository events must be private and may use custom transformers.

---

6. Architecture

Three layers — each must stay in its own boundary:

Presentation  →  Business Logic (Cubit/Bloc)  →  Data (Repository → DataProvider)
  • **Data Layer**: Repositories wrap data providers.
Read more
Ships withflutter-ai-skills

36 Flutter and Dart skills your coding agent loads by itself, sourced only from official documentation. A skill is a folder with a SKILL.md file.

Get the whole plugin
Stats
639
Stars
66
Forks
Active
Maintenance
Shell
Language
MIT
License
3d ago
Last commit
1y ago
Created

Repo: evanca/flutter-ai-rules

Other skills on flutter-ai-skills.