BlocEventStatusGenerator
Code generation for bloc_event_status: annotate a Bloc and get a typed Emitter extension, one method per status.
Installation
dart pub add bloc_event_status
dart pub add --dev bloc_event_status_generator build_runner
Overview
Emitting an event status by hand means rebuilding the state on every call:
emit(
state.copyWith(
eventStatuses: state.eventStatuses.update(event, const LoadingEventStatus()),
),
);
The usual fix is a hand-written Emitter extension (see the bloc_event_status README), which has to be kept in sync with your status type by hand. This package writes that extension for you:
emit.loading(event, state);
Getting Started
Step 1: Annotate your Bloc
Add the part directive and the @blocEventStatus annotation:
import 'package:bloc/bloc.dart';
import 'package:bloc_event_status/bloc_event_status.dart';
part 'todo_bloc.bes.g.dart';
@blocEventStatus
class TodoBloc extends Bloc<TodoEvent, TodoState> {
TodoBloc() : super(const TodoState()) {
on<AddTodo>(_onAddTodo);
}
Future<void> _onAddTodo(AddTodo event, Emitter<TodoState> emit) async {
emit.loading(event, state);
try {
final todos = await _repository.add(event.title);
emit.success(event, state.copyWith(todos: todos));
} on Exception catch (e) {
emit.failure(event, state, e.toString());
}
}
}
Step 2: Run the generator
dart run build_runner build
Or, while developing:
dart run build_runner watch
The builder is applied automatically to any package that depends on it (auto_apply: dependents) and writes *.bes.g.dart next to the source file, so no build.yaml is required in your project.
Requirements
The generator reads everything it needs from your existing types. For a @blocEventStatus-annotated class it expects:
| Requirement | Why |
|---|---|
The class extends Bloc<TEvent, TState> |
TEvent and TState are read from the supertype. |
TState uses EventStatusesMixin<TEvent, TStatus> |
TStatus is read from the mixin. |
TState has a copyWith({eventStatuses}) |
The generated helper emits state.copyWith(eventStatuses: ...). |
TStatus has concrete subtypes in the same library |
One method is generated per concrete subtype. |
A part '<file>.bes.g.dart'; directive |
The extension is generated as a part of your file. |
A complete, runnable setup lives in example/lib/main.dart.
If any of these is missing, the build fails with an explicit message — for example State class must use EventStatusesMixin<TEvent, TStatus>. or No concrete subtypes found for EventStatus.
What Gets Generated
Given this status type and state:
sealed class EventStatus extends Equatable {
const EventStatus();
@override
List<Object?> get props => [];
}
class LoadingEventStatus extends EventStatus {
const LoadingEventStatus();
}
class SuccessEventStatus extends EventStatus {
const SuccessEventStatus();
}
class FailureEventStatus extends EventStatus {
const FailureEventStatus(this.message);
final String message;
@override
List<Object?> get props => [message];
}
class TodoState extends Equatable
with EventStatusesMixin<TodoEvent, EventStatus> {
const TodoState({
this.todos = const [],
this.eventStatuses = const EventStatuses(),
});
final List<String> todos;
@override
final EventStatuses<TodoEvent, EventStatus> eventStatuses;
TodoState copyWith({
List<String>? todos,
EventStatuses<TodoEvent, EventStatus>? eventStatuses,
}) { /* ... */ }
@override
List<Object?> get props => [todos, eventStatuses];
}
todo_bloc.bes.g.dart contains:
extension $TodoBlocEmitterX on Emitter<TodoState> {
void _emitEventStatus<T extends TodoEvent>(
T event,
EventStatus status,
TodoState state,
) {
this(
state.copyWith(
eventStatuses: state.eventStatuses.update(event, status),
),
);
}
void loading<T extends TodoEvent>(T event, TodoState state) =>
_emitEventStatus(event, const LoadingEventStatus(), state);
void success<T extends TodoEvent>(T event, TodoState state) =>
_emitEventStatus(event, const SuccessEventStatus(), state);
void failure<T extends TodoEvent>(T event, TodoState state, String message) =>
_emitEventStatus(event, FailureEventStatus(message), state);
}
Every method takes the event and the state to emit, plus whatever its status constructor needs.
Method names
The method name is the subtype name with the prefix and suffix it shares with the base status type stripped, lowerCamelCased:
| Status base type | Concrete subtype | Method |
|---|---|---|
EventStatus |
LoadingEventStatus |
loading |
CounterEventStatus |
LoadingEventStatus |
loading |
CustomEventStatus |
CustomSuccessEventStatus |
success |
Constructor parameters
Status constructor parameters are forwarded as-is, keeping their kind and defaults:
- required positional → required positional on the method
- optional positional (with or without default) → optional positional
- required named → required named
- optional named (with or without default) → optional named
- generic type parameters (including bounds) → declared on the method, forwarded to the constructor
- a
constconstructor with no arguments →constinstantiation in the generated call
Troubleshooting
Version solving fails on analyzer. Every analyzer release is breaking, so build, build_runner and every generator pin a range. If pub reports that bloc_event_status_generator is forbidden, make sure you are on the latest version of this package — analyzer upper bounds are widened as new releases land. Version 1.1.0 supports analyzer >=8.0.0 <15.0.0 (build_runner up to 2.16.x).
Nothing is generated. Check that the file declares part '<file>.bes.g.dart'; and that the annotated class is a Bloc subclass. dart run build_runner build --verbose shows which builders ran.
--delete-conflicting-outputs is ignored. Recent build_runner versions dropped that option and log a warning when it is passed; plain dart run build_runner build is enough.
VS Code tip
To keep generated *.g.dart files nested under their source file in the Explorer, add this to your .vscode/settings.json:
{
"explorer.fileNesting.patterns": {
"*.dart": "${capture}*.g.dart"
}
}
Libraries
- bloc_event_status_generator
- Code generator for bloc_event_status emitter extensions.
- generator