What's New in Bloc v7.2.0

An overview of the changes introduced in the bloc v7.2.0 release

Open Source
September 21, 2021
updated on
October 18, 2021

Bloc v7.2.0 has been released! It brings an API update and a handful of new features to simplify event transformations. Here’s what’s new in the bloc library, how to migrate, and what’s coming next.

Why Bloc v7.2.0?

We had been wrestling with an issue in Dart for a while that impacted the predictability of state changes when using bloc. We were waiting to see if this would be addressed in the Dart SDK, but because it would require changes to asynchronous constructs including yield* (yield-each), it would be risky and unlikely to be addressed in the near future. Because predictability is at the core of the bloc library, we started to experiment with alternative approaches.

We knew that in order to address the predictability issue, we had to move away from async generators and introduce an API which gave us granular control over when state changes occur. In the current version, mapEventToState returns a Stream which lends itself to relying on async generators (async*) in order to react to events and emit states. The new on<Event> API introduced in v7.2.0 makes it easier and safer to implement complex state changes while eliminating uncertainty.

Before implementing any major updates, we wanted to get input from the bloc community. The initial proposal surfaced some interesting conversations. For example, multiple people expressed concerns about introducing a breaking change without having an easy way to migrate. Instead of making a migration tool, we decided it would be best if the changes were backwards compatible. This way people could gradually migrate one bloc at a time without a looming deadline on the horizon. Shoutout to Marcus Twichel, Dominik Roszkowski, Sam Moore, Christopher Casey, and many others for their feedback.

As a byproduct of the new API, we were able to simplify the public API, align more closely with Cubit, and introduce a simpler way to manage concurrency when using bloc.

Bloc v7.2.0 Updates

✨ Introduce New on<Event> API

The most significant change in v7.2.0 is mapEventToState is deprecated and on<Event> should be used instead.

In v7.1.0:


abstract class CounterEvent {}
class Increment extends CounterEvent {}

class CounterBloc extends Bloc<CounterEvent>, int> {
  CounterBloc() : super(0);

  @override
  Stream<int> mapEventToState(CounterEvent event) async* {
    if (event is Increment) {
      yield state + 1;
    }
  }
}

In v7.2.0:


abstract class CounterEvent {}
class Increment extends CounterEvent {}

class CounterBloc extends Bloc<CounterEvent, int> {
  CounterBloc() : super(0) {
    on<Increment>((event, emit) => emit(state + 1));
  }
}

In practice your existing blocs might look something like:


@override
Stream<State> mapEventToState(Event event) async* {
  if (event is EventA) {
    yield* _mapEventAToState(event);
  } else if (event is EventB) {
    yield* _mapEventBToState(event);
  }
}

Stream<State> _mapEventAToState(EventA event) async* {...}
Stream<State> _mapEventBToState(EventB event) async* {...}

Most of the important logic usually lives inside other functions (e.g. _mapEventAToState and _mapEventBToState), leaving mapEventToState to handle which mapper to call based on the event type. Furthermore, using async generators (async*, yield, yield*) can introduce complexity and undesired behavior in some cases.

In v7.2.0 the above bloc would look something like:


class SomeBloc extends Bloc<Event, State> {
  SomeBloc() : super(0) {
    on<EventA>(_onEventA);
    on<EventB>(_onEventB);
  }
  
  void _onEventA(EventA event, Emitter<State> emit) {...}
  void _onEventB(EventB event, Emitter<State> emit) {...}
}

You can register event handlers by calling on<Event> where Event is the type of event being handled. As seen above, the on<Event> API provides a callback (Event event, Emitter<State>) {...} which is invoked when the particular event is added to the bloc. You are then able to emit zero or more states using the Emitter provided by the API. Calling emit(...) updates the bloc's state synchronously.

✨ Introduce New EventTransformer API

In addition to on<Event>, transformEvents is deprecated and the new EventTransformer API should be used instead.

Previously, if you wanted to provide a custom event transformer per event you would need to introduce further complexity within the transformEvents function. The on<Event> API allows you to provide a custom event transformer per event handler in a more streamlined and developer-friendly way.

In v7.1.0:


@override
Stream<Transition<MyEvent, MyState>> transformEvents(events, transitionFn) {
  return events
    .debounceTime(const Duration(milliseconds: 300))
    .flatMap(transitionFn);
}

In v7.2.0:


EventTransformer<MyEvent> debounce<MyEvent>(Duration duration) {
  return (events, mapper) => events.debounceTime(duration).flatMap(mapper);
}

MyBloc() : super(MyState()) {
  on<MyEvent>(_onEvent, transformer: debounce(const Duration(milliseconds: 300)))
}

As seen above, a custom EventTransformer is responsible for taking the incoming stream of events along with an EventMapper (your event handler) and returning a new stream of events.

📦 New Package: bloc_concurrency

The new package bloc_concurrency comes with an opinionated set of custom event transformers.

Out of the box, it includes:

  • concurrent - process events concurrently
  • sequential - process events sequentially
  • droppable - ignore any events added while an event is processing
  • restartable - process only the latest event and cancel previous event handlers

Using the custom event transformers is as easy as:


import 'package:bloc/bloc.dart';
import 'package:bloc_concurrency/bloc_concurrency.dart';

abstract class CounterEvent {}
class Increment extends CounterEvent {}

class CounterBloc extends Bloc<CounterEvent, int> {
  CounterBloc() : super(0) {
    on<Increment>(
      (event, emit) async {
        await Future.delayed(Duration(seconds: 1));
        emit(state + 1);
      },
      /// Specify a custom event transformer from `package:bloc_concurrency`
      transformer: sequential(),
    );
  }
}

⚠️ Deprecate transformTransitions API

The transformTransitions API is deprecated and the state stream should be used instead.

In v7.1.0:


@override
Stream<Transition<Event, State>> transformTransitions(
  Stream<Transition<Event, State>> transitions,
) {
  return transitions.debounceTime(const Duration(milliseconds: 42));
}

In v7.2.0:


@override
Stream<State> get stream => super.stream.debounceTime(const Duration(milliseconds: 42));


Migration Strategy

Migrating to the latest version of bloc is quite simple since version v7.2.0 is backwards compatible. Using the new on<Event> API, you can migrate one bloc at a time. Changes to the bloc will not impact UI components or existing tests. Once the new API changes are complete, you can simply rerun your test suite to ensure your bloc migration was successful. Additionally, the backwards compatibility of bloc v7.2.0 means you can run your application even when some blocs are still using the previous mapEventToState API and others are using the updated on<Event> API. This feature allows for incremental development while maintaining a fully functioning application during your migration to the new bloc API.

Once all of the blocs in your application are transitioned to the new on<Event> API, you are ready to upgrade your pubspec.yaml file to bloc v8.0.0 (coming soon). In v8.0.0, deprecated APIs, like mapEventToState, will no longer be supported. Consequently, it is important to make sure your project is fully migrated before moving to bloc v8.0.0. For further information on bloc migration, refer to our migration guide.

Coming Soon: bloc v8.0.0

All bloc tutorials and example applications have already been migrated to the new APIs and new releases for developer tooling (VSCode and IntelliJ extensions) are available so you can get ahead of the upcoming major release.

Speaking of v8.0.0, we're planning to release a developer preview in the coming weeks with a stable v8.0.0 release sometime in October 🤞. Developement for v8.0.0 is already well underway but as it is a major release, we want to make sure we're covering all our bases across the entire bloc ecosystem. If you have any feature requests, questions, or feedback now is a great time to open an issue on GitHub.

Read more about why we use flutter_bloc for state management.

Additional Contributors

More Stories