bus-event
Handles cross-feature BusEvent communication with EventBusManager in the Flutter base template
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Handles cross-feature BusEvent communication with EventBusManager in the Flutter base template
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
| name | bus-event |
| description | Handles cross-feature BusEvent communication with EventBusManager in the Flutter base template |
| license | MIT |
| compatibility | all |
| metadata | {"audience":"flutter-developers","framework":"flutter","pattern":"event-bus"} |
BusEvent classes.Do not use bus events for normal parent-child widget callbacks, local UI-only state, route results, or logic that belongs inside one BLoC event/state flow.
BusEvent is the app-wide notification mechanism for cross-module synchronization. EventBusManager wraps the event_bus package in core/lib/common/components/event_bus/event_bus.dart and exposes:
Stream<T> on<T extends BusEvent>();
void fire(BusEvent event);
EventBusManager is registered as a singleton through injectable, so publishers and subscribers receive the same stream instance. Import package:core/core.dart to access BusEvent and EventBusManager.
Representative uses:
add(...); mutate state only inside registered BLoC handlers.StreamSubscription, call cancel() in close(), and await the returned Future when close() is async.on<BusEvent>() listeners — subscribe to the narrowest event type possible.Put general domain event files under:
apps/main/lib/domain/bus_event/
For feature/use-case scoped events, place the file near the owning domain use case and keep the location consistent within that feature:
apps/main/lib/domain/usecases/<feature>/<feature>_bus_event.dart
Use this when the payload shape is the same and listeners branch by the completed change type.
import 'package:core/core.dart';
import '../../entities/record/record_history_log.dart';
enum RecordActivityChangeType { created, updated }
class RecordActivityChangedBusEvent extends BusEvent {
final RecordHistoryLog log;
final RecordActivityChangeType type;
RecordActivityChangedBusEvent({
required this.log,
required this.type,
});
}
Use this when related events have different payloads but subscribers often handle the group.
import 'package:core/core.dart';
import '../../entities/post/post_model.dart';
abstract class PostBusEvent extends BusEvent {}
abstract class PostUpdatedBusEvent extends PostBusEvent {
PostModel get post;
}
class PostEditedBusEvent extends PostUpdatedBusEvent {
@override
final PostModel post;
PostEditedBusEvent({required this.post});
}
class PostInteractionUpdatedBusEvent extends PostUpdatedBusEvent {
@override
final PostModel post;
PostInteractionUpdatedBusEvent({required this.post});
}
class PostDeletedBusEvent extends PostBusEvent {
final String postId;
PostDeletedBusEvent({required this.postId});
}
Inject EventBusManager into the use case or BLoC that owns the successful operation, then call fire() after the state-changing work succeeds.
@Injectable(as: DeletePostUsecase)
class DeletePostInteractor implements DeletePostUsecase {
final PostRepository _repository;
final EventBusManager _eventBusManager;
DeletePostInteractor(this._repository, this._eventBusManager);
@override
Future<void> deletePost(String postId) async {
await _repository.deletePost(postId: postId);
_eventBusManager.fire(PostDeletedBusEvent(postId: postId));
}
}
For background work, fire progress/completion/failure events only when the task meaningfully changes. Use a distinct check, throttling, or batching for high-frequency progress updates.
void _updateTask(String taskId, PostingTask task) {
if (_tasks[taskId] == task) return;
_tasks[taskId] = task;
_notifyTasksChanged();
_eventBusManager.fire(PostingTaskUpdatedBusEvent(task: task));
}
Declare a typed subscription, subscribe in the constructor after registering BLoC event handlers, and delegate to a listener method.
@Injectable()
class RecordsListingBloc
extends AppBlocBase<RecordsListingEvent, RecordsListingState> {
final RecordsUsecase _usecase;
StreamSubscription<RecordActivityChangedBusEvent>? _eventSubscription;
RecordsListingBloc(
this._usecase,
EventBusManager eventBusManager,
) : super(RecordsListingInitial(data: const _StateData())) {
on<GetRecordsEvent>(_onGetRecordsEvent);
on<ActivityCreatedEvent>(_onActivityCreatedEvent);
on<ActivityUpdatedEvent>(_onActivityUpdatedEvent);
_eventSubscription = eventBusManager.on<RecordActivityChangedBusEvent>().listen(
_recordActivityBusListener,
);
}
void _recordActivityBusListener(RecordActivityChangedBusEvent event) {
switch (event.type) {
case RecordActivityChangeType.created:
add(ActivityCreatedEvent(log: event.log));
break;
case RecordActivityChangeType.updated:
add(ActivityUpdatedEvent(log: event.log));
break;
}
}
@override
Future<void> close() async {
await _eventSubscription?.cancel();
await super.close();
}
}
If the bloc file does not already import dart:async, add it for StreamSubscription.
When subscribing to an abstract event group, type-check the handled variants in one listener and add BLoC events for actual state changes.
StreamSubscription<PostBusEvent>? _postEventBusSubscription;
_postEventBusSubscription = _eventBusManager.on<PostBusEvent>().listen(
_listenToPostEvents,
);
void _listenToPostEvents(PostBusEvent event) {
if (event is PostDeletedBusEvent) {
add(RemovePostEvent(postId: event.postId));
} else if (event is PostUpdatedBusEvent) {
add(UpdatePostEvent(post: event.post));
}
}
Prefer separate subscriptions when handlers are unrelated or strongly typed callbacks make the code simpler.
| Scenario | Payload |
|---|---|
| Remove an item | ID only, e.g. postId, adId, blockedUserId |
| Replace/update an item in a list | Full updated model |
| Refresh a list | Small change-type enum or marker event |
| Sync counters/statistics | ID plus updated counter values |
| Background task status | Task entity plus optional result model/error data |
| Profile or account switch | Selected user/profile object or changed field |
Avoid passing UI BuildContext, widgets, BLoC instances, repositories, callbacks, or mutable collections through bus events.
In tests, construct a real manager with a local event bus unless you need to verify fire() calls directly:
final eventBusManager = EventBusManager(EventBus());
For BLoC tests that listen to bus events, fire the event through the same manager instance used by the BLoC, then assert that the BLoC emits the expected state or effect.
BusEvent directly or through a domain-specific abstract base.domain/bus_event/ or beside the owning use case following existing patterns._eventBusManager.fire(...) only after successful work or task-state updates.on<T>() type.add(...) instead of emitting directly from the stream callback.StreamSubscription is stored and canceled in close().EventBusManager instance for publisher and subscriber.on<BusEvent>() when on<PostBusEvent>() or on<SpecificEvent>() is enough.close().Implements BLoC state management using AppBlocBase, an abstract State hierarchy, and a freezed _StateData
Builds the data layer with Freezed DTOs, Retrofit clients, hive_ce local stores, and repositories wired through injectable
Reviews UI-layer changes — screens, blocs, widgets, routes — against the template's StateBase + AppBlocBase + fl_theme conventions
Adds and updates app strings through the CSV → ARB → generated localizations workflow
Scaffolds a new feature module under apps/main/lib/presentation/modules using the bundled module generator
Configures routes with the IRoute / CustomRouter abstractions in core and exposes navigation via a BuildContext coordinator