Issue
Let’s assume we have an app where user has a calendar where he can select a date for which he wants to get list of events. When user selects the date, we add an event CalendarSelectedDateChanged(DateTime)
. Bloc component fetches data from API each time we receive such an event. We can imagine a situation when the response is received with different delay. This will produce the following scenario:
- User clicks date 1
- API call is made with param date=1
- State is set to loading
- User clicks date 2
- State is set to loading
- Response from request date=2 is received
- State is set to dataLoadSuccess(date=2)
Expected result is that we discard response from request date=1 here
- Response from request date=1 is received
- State is set to dataLoadSuccess(date=1)
How can we resolve such a race condition in the most elegant way (preferably for all BLoCs in the app at once)?
Here’s an exemplary code that will produce such an issue:
class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
ExampleBloc()
: super(ExampleDataLoadInProgress(DateTime.now())) {
on<ExampleSelectedDateChanged>((event, emit) async {
await _fetchData(event.date, emit);
});
}
Future<void> _fetchData(DateTime selectedDate,
Emitter<ExampleState> emit,) async {
emit(ExampleDataLoadInProgress(selectedDateTime));
try {
final data = callAPI(selectedDateTime);
emit(ExampleDataLoadSuccess(data, selectedDate));
} on ApiException catch (e) {
emit(ExampleDataLoadFailure(e, selectedDateTime));
}
}
}
Solution
By default all events are processed concurrently, you can change that behavior by setting a custom transformer:
You can read more about transformers here: https://pub.dev/packages/bloc_concurrency
class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
ExampleBloc()
: super(ExampleDataLoadInProgress(DateTime.now())) {
on<ExampleSelectedDateChanged>((event, emit) async {
await _fetchData(event.date, emit);
},
transformer: restartable(), // ADD THIS LINE
);
}
Future<void> _fetchData(DateTime selectedDate,
Emitter<ExampleState> emit,) async {
emit(ExampleDataLoadInProgress(selectedDateTime));
try {
final data = callAPI(selectedDateTime);
emit(ExampleDataLoadSuccess(data, selectedDate));
} on ApiException catch (e) {
emit(ExampleDataLoadFailure(e, selectedDateTime));
}
}
}
Answered By – RMK
Answer Checked By – Robin (FlutterFixes Admin)