Errors and observers¶
Let an unexpected error escape applyAsync. FF Bloc keeps error conversion and
reporting consistent across events.
Error path¶
An uncaught event-stream error follows this order:
Bloc.observer.onErrorreceives the bloc, original error, and stack trace.onErrorObserverreceives the event, error, and stack trace.onErrorStateconverts the error into the next state.
Both FFBloc and FFGenericBloc use this path.
Produce a visible error state¶
Clear loading because status precedence puts loading before error. copy keeps
existing data, which is useful when the UI wants to show a retry surface over a
previous model.
Use copyWithoutData(isLoading: false, error: error) when a failure makes old
data invalid.
Add feature context¶
@override
void onObserver({required ProfileEvent event}) {
logger.info('profile event: $event');
}
@override
void onErrorObserver({
required ProfileEvent event,
required Object error,
required StackTrace stackTrace,
}) {
logger.error(
'profile event failed: $event',
error: error,
stackTrace: stackTrace,
);
}
onObserver runs only when the event begins handling. A droppable event that is
rejected while another event is active does not reach the hook.
Avoid duplicate reporting¶
The global and feature-specific hooks both see the same error. Decide which layer sends telemetry:
- use the global observer for uniform crash/error reporting;
- use the feature hook for structured local logs or context;
- do not send the same exception to the same backend from both layers.
The error is already reported to Bloc.observer; do not throw again from
onErrorState merely to reach global telemetry.
Preserve the original error¶
onErrorState receives the original object. Store or map it deliberately:
@override
ProfileState onErrorState(Object error) {
final visibleError = error is ProfileNotFound
? const ProfileMessage.notFound()
: ProfileMessage.unexpected(error);
return state.copy(isLoading: false, error: visibleError);
}
Observers still receive the original error and stack trace before mapping.
Transition observation¶
Use onTransitionObserver for state-change diagnostics:
@override
void onTransitionObserver({
required Transition<ProfileEvent, ProfileState> transition,
}) {
logger.debug('profile transition: $transition');
}
Keep observer hooks side-effect-light. A slow logger delays event processing and can change perceived concurrency.