Event concurrency¶
Every FF Bloc registers one handler for its base event type. The
FFEventConcurrency value determines how that handler schedules overlapping
events.
Policies¶
| Policy | New event while active | State order | Typical use |
|---|---|---|---|
sequential |
Queued | Event order | Writes, imports, multi-step workflows |
concurrent |
Starts immediately | Completion order | Independent reads |
droppable |
Ignored | Current event only | Submit, refresh, expensive duplicate action |
restartable |
Replaces active handler | Latest subscribed handler | Search, autocomplete, selection-driven load |
Sequential is the default and preserves the package's existing behavior.
Configure the bloc¶
class SearchBloc extends FFBloc<SearchEvent, SearchState> {
SearchBloc({required this.repository})
: super(
initialState: const SearchState(),
eventConcurrency: FFEventConcurrency.restartable,
);
final SearchRepository repository;
@override
SearchState onErrorState(Object error) =>
state.copy(isLoading: false, error: error);
}
The same constructor parameter is available on FFGenericBloc.
Choose from semantics¶
Use when every event must happen and order changes the result. A second save should not overtake the first save.
Use when operations are independent and completion order is acceptable. A slower earlier event may emit after a faster later event.
Use when repeated input during an active operation has no additional
meaning. A dropped event never enters applyAsync or onObserver.
Use when only the newest result remains relevant. The prior event stream is cancelled before the new stream becomes authoritative.
Cancellation boundary¶
Restartable cancels the stream subscription used by the event handler. It does not automatically cancel work owned by an external API:
- an HTTP request may still reach the server;
- a database write may still commit;
- a service future may still complete;
- logging or analytics already emitted remain emitted.
Use cancellation-aware repositories, request tokens, generation checks, or idempotency where the side effect itself must be bounded. Do not use restartable for irreversible writes merely to make the UI feel responsive.
One policy per bloc¶
Because FF Bloc registers one handler for the base event type, the constructor policy applies to every event subtype. When search should be restartable but save must be sequential, prefer separate focused blocs.
Existing subclasses may override transform for a deliberate custom rule:
@override
Stream<SearchEvent> transform(
Stream<SearchEvent> events,
Stream<SearchEvent> Function(SearchEvent) mapper,
) {
return FFEventConcurrency.restartable
.createTransformer<SearchEvent>()
.call(events, mapper);
}
Keep a regression test for a custom transformer. The constructor policy is clearer when a standard behavior is sufficient.
Test overlap explicitly¶
Use controlled streams or completers instead of real delays:
- Start the first event and wait until its operation begins.
- Add the second event before finishing the first.
- Assert whether the second starts, queues, or is dropped.
- Complete operations in a deliberate order.
- Assert the final state and any cancellation signal.
The package test suite covers all four policies with controlled event streams.