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 職業分類に基づく
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
| 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().