Flutter BLoC event race condition

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)

Leave a Reply

Your email address will not be published.