Use when working on accessibility, a11y, WCAG, ARIA, screen readers, keyboard nav, focus order, contrast, alt text, captions, reduced motion, or target sizes; not language/culture/device (see inclusive-design).
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
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.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
설치 방법 보기설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.
Design, implement, and test state management using the bloc and flutter_bloc libraries.
Use this skill when:
BlocBuilder, BlocListener, BlocConsumer, or BlocProvider in the widget tree.| 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.
LoginButtonPressed, UserProfileLoaded.BlocSubject + optional noun + verb.BlocSubjectStarted (e.g., AuthenticationStarted).BlocSubjectEvent.BlocSubjectState.BlocSubject + Initial | InProgress | Success | Failure.
LoginInitial, LoginInProgress, LoginSuccess, LoginFailure.BlocSubjectState + BlocSubjectStatus enum (initial, loading, success, failure).switch is desired.@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): ...
}
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];
}
Equatable and pass all relevant fields to props.List/Map properties with List.of/Map.of inside props.@immutable.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:
emit inside the Cubit/Bloc.void or Future<void> only.storage in a HydratedCubit, pass it as a named parameter: super(initialState, storage: storage).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:
bloc.add(Event()), not custom public methods._onEventName).Three layers — each must stay in its own boundary:
Presentation → Business Logic (Cubit/Bloc) → Data (Repository → DataProvider)
Rules:
BlocListener in the UI to bridge blocs.BlocObserver in main.dart.| Widget | Use |
|---|---|
BlocProvider | Provide a bloc to a subtree |
MultiBlocProvider | Provide multiple blocs without nesting |
BlocBuilder | Rebuild UI on state change |
BlocListener | Side effects only (navigation, dialogs, snackbars) |
MultiBlocListener | Listen to multiple blocs without nesting |
BlocConsumer | Rebuild UI + side effects together |
BlocSelector | Rebuild only when a selected slice of state changes |
RepositoryProvider | Provide a repository to the widget tree |
MultiRepositoryProvider | Provide multiple repositories without nesting |
BlocProvider(
create: (context) => LoginCubit(context.read<AuthRepository>()),
child: LoginView(),
);
BlocBuilder<LoginCubit, LoginState>(
builder: (context, state) {
return switch (state.status) {
LoginStatus.loading => const CircularProgressIndicator(),
LoginStatus.success => const HomeView(),
LoginStatus.failure => Text(state.errorMessage ?? 'Error'),
LoginStatus.initial => const LoginForm(),
};
},
);
BlocListener<LoginCubit, LoginState>(
listener: (context, state) {
if (state.status == LoginStatus.failure) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(state.errorMessage ?? 'Login failed')),
);
}
},
child: LoginForm(),
);
Rules:
context.read<T>() in callbacks (not in build).context.watch<T>() in build only when necessary; prefer BlocBuilder.context.watch or context.select at the root of build — scope with Builder.Use bloc_test package. Mock repositories with mocktail.
import 'package:bloc_test/bloc_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:test/test.dart';
class MockAuthRepository extends Mock implements AuthRepository {}
void main() {
group('LoginCubit', () {
late AuthRepository authRepository;
late LoginCubit loginCubit;
setUp(() {
authRepository = MockAuthRepository();
loginCubit = LoginCubit(authRepository);
});
tearDown(() => loginCubit.close());
test('initial state should be LoginState with status initial', () {
expect(loginCubit.state, const LoginState());
});
blocTest<LoginCubit, LoginState>(
'should emit [loading, success] when login succeeds',
build: () {
when(() => authRepository.login(any(), any()))
.thenAnswer((_) async => fakeUser);
return loginCubit;
},
act: (cubit) => cubit.login('email@test.com', 'password'),
expect: () => [
const LoginState(status: LoginStatus.loading),
LoginState(status: LoginStatus.success, user: fakeUser),
],
);
blocTest<LoginCubit, LoginState>(
'should emit [loading, failure] when login throws',
build: () {
when(() => authRepository.login(any(), any()))
.thenThrow(Exception('error'));
return loginCubit;
},
act: (cubit) => cubit.login('email@test.com', 'wrong'),
expect: () => [
const LoginState(status: LoginStatus.loading),
isA<LoginState>().having((s) => s.status, 'status', LoginStatus.failure),
],
);
});
}
Rules:
tearDown(() => cubit.close()).blocTest for state emission assertions.group() named after the class under test.registerFallbackValue(MyEvent()).| Pitfall | Fix |
|---|---|
| Emitting the same state instance twice | Always create a new state object; bloc ignores duplicate emissions via ==. |
Calling context.watch inside callbacks | Use context.read in callbacks; watch is only valid inside build. |
Forgetting Equatable props | Add every field to props; missing fields cause silent state update bugs. |
| Mutable state fields | Keep state @immutable; use copyWith or new sealed subclass instances. |
| Business logic in widgets | Move all logic into the Cubit/Bloc; widgets only dispatch events or call methods. |
// BAD — mutating state in-place
state.items.add(newItem);
emit(state);
// GOOD — emit a new state with copied list
emit(state.copyWith(items: [...state.items, newItem]));
아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.
로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.
아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.
Use when working on accessibility, a11y, WCAG, ARIA, screen readers, keyboard nav, focus order, contrast, alt text, captions, reduced motion, or target sizes; not language/culture/device (see inclusive-design).
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use when creating a feature, designing folder structure, adding repositories/services/view models, wiring dependency injection, or deciding which layer owns logic.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use when asked to review a PR, MR, branch, or diff, audit changed files, or check code quality.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use when writing switch statements, refactoring if-else chains, creating data classes, choosing records vs classes, destructuring values, or modernizing pre-Dart-3 code.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use when building AI agents in Dart, implementing Genkit flows or tools, integrating LLMs into Dart or Flutter applications, or using Genkit Dart plugins.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use when writing Dart code, reviewing for style, refactoring naming, adding doc comments, structuring imports, or enforcing type annotations.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.