Bloc: on<Event>, emit and handler rules
Bloc is, in the docs' words, a more advanced class which relies on events to trigger state changes rather than functions. The structure:
- Event classes are declared (usually a
sealed classplus subclasses). - A handler is registered per event type in the constructor:
on<CounterIncrementPressed>((event, emit) => emit(state + 1)); - The UI adds events:
context.read<CounterBloc>().add(CounterIncrementPressed()).
Practical rules about handlers:
- One handler per event type; registering a second
on<T>for the same type is a mistake. emitworks inside the handler, including after anawait; but you must not keep theemitand use it after the handler completes.- To turn a stream into state there are
emit.forEachandemit.onEach— the subscription is managed together with the handler.
dart
// Event-lər keçmiş zamanda adlandırılır (rəsmi konvensiya).
sealed class TodoEvent {}
final class TodoStarted extends TodoEvent {}
final class TodoAdded extends TodoEvent {
TodoAdded(this.title);
final String title;
}
final class TodoDeleted extends TodoEvent {
TodoDeleted(this.id);
final String id;
}
class TodoBloc extends Bloc<TodoEvent, TodoState> {
TodoBloc(this._repository) : super(const TodoState()) {
on<TodoStarted>(_onStarted);
on<TodoAdded>(_onAdded);
on<TodoDeleted>(_onDeleted);
}
final TodoRepository _repository;
Future<void> _onStarted(TodoStarted event, Emitter<TodoState> emit) async {
emit(state.copyWith(status: TodoStatus.loading));
try {
final todos = await _repository.fetchAll();
emit(state.copyWith(status: TodoStatus.success, todos: todos));
} catch (error) {
emit(state.copyWith(status: TodoStatus.failure, error: '$error'));
}
}
Future<void> _onAdded(TodoAdded event, Emitter<TodoState> emit) async {
await _repository.create(event.title);
add(TodoStarted()); // yenidən yükləmə üçün event əlavə etmək məqbul yanaşmadır
}
Future<void> _onDeleted(TodoDeleted event, Emitter<TodoState> emit) async {
emit(state.copyWith(
todos: state.todos.where((t) => t.id != event.id).toList(),
));
await _repository.remove(event.id);
}
}Sealed events, handlers and async work.
dart
class ChatBloc extends Bloc<ChatEvent, ChatState> {
ChatBloc(this._repository) : super(const ChatState()) {
on<ChatSubscriptionRequested>(_onSubscriptionRequested);
}
final ChatRepository _repository;
Future<void> _onSubscriptionRequested(
ChatSubscriptionRequested event,
Emitter<ChatState> emit,
) async {
// Stream-in hər elementi state-ə çevrilir; abunəlik handler ilə
// birlikdə bağlanır — əl ilə StreamSubscription saxlamağa ehtiyac yoxdur.
await emit.forEach<List<Message>>(
_repository.watchMessages(),
onData: (messages) => state.copyWith(messages: messages),
onError: (error, stackTrace) => state.copyWith(error: '$error'),
);
}
}`emit.forEach` — turning a stream into state.
The official naming conventions (especially recommended for large teams):
- Events are named in the past tense, because from the bloc's perspective they have already happened:
BlocSubject+ noun (optional) + verb. The initial load event:BlocSubject+Started. The base event class:BlocSubject+Event. - States are nouns, because a state is a snapshot at a point in time. When represented as subclasses:
BlocSubject+ verb +State, whereStateis one ofInitial,Success,FailureorInProgress.
📚 Sources and documentation
- Bloc conceptsofficialbloclibrary.dev
on<Event>, Emitter, emit.forEach and the Cubit/Bloc difference.
- Naming conventionsofficialbloclibrary.dev
The official rules for naming events and states — a reference point in code review.
- Bloc architectureofficialbloclibrary.dev
- package:blocofficialpub.dev