bloc_event_status_generator 1.1.0
bloc_event_status_generator: ^1.1.0 copied to clipboard
Code generator for bloc_event_status: turns a @blocEventStatus-annotated Bloc into a typed Emitter extension with one method per event status.
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"
}
}