Migration Guide
هذا المحتوى غير متوفر بلغتك بعد.
v10.0.0
Section titled “v10.0.0”package:bloc_test
Section titled “package:bloc_test”❗✨ Decouple blocTest from BlocBase
Section titled “❗✨ Decouple blocTest from BlocBase”Rationale
Section titled “Rationale”blocTest should use the core bloc interfaces when possible for increased
flexibility and reusability. Previously this wasn’t possible because BlocBase
implemented StateStreamableSource which was not enough for blocTest due to
the internal dependency on the emit API.
package:hydrated_bloc
Section titled “package:hydrated_bloc”❗✨ Support WebAssembly
Section titled “❗✨ Support WebAssembly”Rationale
Section titled “Rationale”It was previously not possible to compile apps to wasm when using
hydrated_bloc. In v10.0.0, the package was refactored to allow compiling to
wasm.
v9.x.x
Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); HydratedBloc.storage = await HydratedStorage.build( storageDirectory: kIsWeb ? HydratedStorage.webStorageDirectory : await getTemporaryDirectory(), ); runApp(App());}v10.x.x
void main() async { WidgetsFlutterBinding.ensureInitialized(); HydratedBloc.storage = await HydratedStorage.build( storageDirectory: kIsWeb ? HydratedStorageDirectory.web : HydratedStorageDirectory((await getTemporaryDirectory()).path), ); runApp(const App());}v9.0.0
Section titled “v9.0.0”package:bloc
Section titled “package:bloc”❗🧹 Remove Deprecated APIs
Section titled “❗🧹 Remove Deprecated APIs”Summary
Section titled “Summary”BlocOverridesremoved in favor ofBloc.observerandBloc.transformer
❗✨ Introduce new EmittableStateStreamableSource Interface
Section titled “❗✨ Introduce new EmittableStateStreamableSource Interface”Rationale
Section titled “Rationale”package:bloc_test was previously tightly coupled to BlocBase. The
EmittableStateStreamableSource interface was introduced in order to allow
blocTest to be decoupled from the BlocBase concrete implementation.
package:hydrated_bloc
Section titled “package:hydrated_bloc”✨ Reintroduce HydratedBloc.storage API
Section titled “✨ Reintroduce HydratedBloc.storage API”Rationale
Section titled “Rationale”Refer to the rationale for reintroducing the Bloc.observer and Bloc.transformer overrides.
v8.x.x
Future<void> main() async { final storage = await HydratedStorage.build( storageDirectory: kIsWeb ? HydratedStorage.webStorageDirectory : await getTemporaryDirectory(), ); HydratedBlocOverrides.runZoned( () => runApp(App()), storage: storage, );}v9.0.0
Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); HydratedBloc.storage = await HydratedStorage.build( storageDirectory: kIsWeb ? HydratedStorage.webStorageDirectory : await getTemporaryDirectory(), ); runApp(App());}v8.1.0
Section titled “v8.1.0”package:bloc
Section titled “package:bloc”✨ Reintroduce Bloc.observer and Bloc.transformer APIs
Section titled “✨ Reintroduce Bloc.observer and Bloc.transformer APIs”Rationale
Section titled “Rationale”The BlocOverrides API was introduced in v8.0.0 in an attempt to support
scoping bloc-specific configurations such as BlocObserver, EventTransformer,
and HydratedStorage. In pure Dart applications, the changes worked well;
however, in Flutter applications the new API caused more problems than it
solved.
The BlocOverrides API was inspired by similar APIs in Flutter/Dart:
Problems
While it wasn’t the primary reason for these changes, the BlocOverrides API
introduced additional complexity for developers. In addition to increasing the
amount of nesting and lines of code needed to achieve the same effect, the
BlocOverrides API required developers to have a solid understanding of
Zones in Dart.
Zones are not a beginner-friendly concept and failure to understand how Zones
work could lead to the introduction of bugs (such as uninitialized observers,
transformers, storage instances).
For example, many developers would have something like:
void main() { WidgetsFlutterBinding.ensureInitialized(); BlocOverrides.runZoned(...);}The above code, while appearing harmless, can actually lead to many difficult to
track bugs. Whatever zone WidgetsFlutterBinding.ensureInitialized is initially
called from will be the zone in which gesture events are handled (e.g. onTap,
onPressed callbacks) due to GestureBinding.initInstances. This is just one
of many issues caused by using zoneValues.
In addition, Flutter does many things behind the scenes which involve forking/manipulating Zones (especially when running tests) which can lead to unexpected behaviors (and in many cases behaviors that are outside the developer’s control — see issues below).
Due to the use of the
runZoned, the
transition to the BlocOverrides API led to the discovery of several
bugs/limitations in Flutter (specifically around Widget and Integration Tests):
- https://github.com/flutter/flutter/issues/96939
- https://github.com/flutter/flutter/issues/94123
- https://github.com/flutter/flutter/issues/93676
which affected many developers using the bloc library:
- https://github.com/felangel/bloc/issues/3394
- https://github.com/felangel/bloc/issues/3350
- https://github.com/felangel/bloc/issues/3319
v8.0.x
void main() { BlocOverrides.runZoned( () { // ... }, blocObserver: CustomBlocObserver(), eventTransformer: customEventTransformer(), );}v8.1.0
void main() { Bloc.observer = CustomBlocObserver(); Bloc.transformer = customEventTransformer();
// ...}v8.0.0
Section titled “v8.0.0”package:bloc
Section titled “package:bloc”❗✨ Introduce new BlocOverrides API
Section titled “❗✨ Introduce new BlocOverrides API”Rationale
Section titled “Rationale”The previous API used to override the default BlocObserver and
EventTransformer relied on a global singleton for both the BlocObserver and
EventTransformer.
As a result, it was not possible to:
- Have multiple
BlocObserverorEventTransformerimplementations scoped to different parts of the application - Have
BlocObserverorEventTransformeroverrides be scoped to a package- If a package were to depend on
package:blocand registered its ownBlocObserver, any consumer of the package would either have to overwrite the package’sBlocObserveror report to the package’sBlocObserver.
- If a package were to depend on
It was also more difficult to test because of the shared global state across tests.
Bloc v8.0.0 introduces a BlocOverrides class which allows developers to
override BlocObserver and/or EventTransformer for a specific Zone rather
than relying on a global mutable singleton.
v7.x.x
void main() { Bloc.observer = CustomBlocObserver(); Bloc.transformer = customEventTransformer();
// ...}v8.0.0
void main() { BlocOverrides.runZoned( () { // ... }, blocObserver: CustomBlocObserver(), eventTransformer: customEventTransformer(), );}Bloc instances will use the BlocObserver and/or EventTransformer for the
current Zone via BlocOverrides.current. If there are no BlocOverrides for
the zone, they will use the existing internal defaults (no change in
behavior/functionality).
This allows allow each Zone to function independently with its own
BlocOverrides.
BlocOverrides.runZoned( () { // BlocObserverA and eventTransformerA final overrides = BlocOverrides.current;
// Blocs in this zone report to BlocObserverA // and use eventTransformerA as the default transformer. // ...
// Later... BlocOverrides.runZoned( () { // BlocObserverB and eventTransformerB final overrides = BlocOverrides.current;
// Blocs in this zone report to BlocObserverB // and use eventTransformerB as the default transformer. // ... }, blocObserver: BlocObserverB(), eventTransformer: eventTransformerB(), ); }, blocObserver: BlocObserverA(), eventTransformer: eventTransformerA(),);❗✨ Improve Error Handling and Reporting
Section titled “❗✨ Improve Error Handling and Reporting”Rationale
Section titled “Rationale”The goal of these changes is:
- make internal unhandled exceptions extremely obvious while still preserving bloc functionality
- support
addErrorwithout disrupting control flow
Previously, error handling and reporting varied depending on whether the
application was running in debug or release mode. In addition, errors reported
via addError were treated as uncaught exceptions in debug mode which led to a
poor developer experience when using the addError API (specifically when
writing unit tests).
In v8.0.0, addError can be safely used to report errors and blocTest can be
used to verify that errors are reported. All errors are still reported to
onError, however, only uncaught exceptions are rethrown (regardless of debug
or release mode).
❗🧹 Make BlocObserver abstract
Section titled “❗🧹 Make BlocObserver abstract”Rationale
Section titled “Rationale”BlocObserver was intended to be an interface. Since the default API
implementation are no-ops, BlocObserver is now an abstract class to clearly
communicate that the class is meant to be extended and not directly
instantiated.
v7.x.x
void main() { // It was possible to create an instance of the base class. final observer = BlocObserver();}v8.0.0
class MyBlocObserver extends BlocObserver {...}
void main() { // Cannot instantiate the base class. final observer = BlocObserver(); // ERROR
// Extend `BlocObserver` instead. final observer = MyBlocObserver(); // OK}❗✨ add throws StateError if Bloc is closed
Section titled “❗✨ add throws StateError if Bloc is closed”Rationale
Section titled “Rationale”Previously, it was possible to call add on a closed bloc and the internal
error would get swallowed, making it difficult to debug why the added event was
not being processed. In order to make this scenario more visible, in v8.0.0,
calling add on a closed bloc will throw a StateError which will be reported
as an uncaught exception and propagated to onError.
❗✨ emit throws StateError if Bloc is closed
Section titled “❗✨ emit throws StateError if Bloc is closed”Rationale
Section titled “Rationale”Previously, it was possible to call emit within a closed bloc and no state
change would occur but there would also be no indication of what went wrong,
making it difficult to debug. In order to make this scenario more visible, in
v8.0.0, calling emit within a closed bloc will throw a StateError which will
be reported as an uncaught exception and propagated to onError.
❗🧹 Remove Deprecated APIs
Section titled “❗🧹 Remove Deprecated APIs”Summary
Section titled “Summary”mapEventToStateremoved in favor ofon<Event>transformEventsremoved in favor ofEventTransformerAPITransitionFunctiontypedef removed in favor ofEventTransformerAPIlistenremoved in favor ofstream.listen
package:bloc_test
Section titled “package:bloc_test”✨ MockBloc and MockCubit no longer require registerFallbackValue
Section titled “✨ MockBloc and MockCubit no longer require registerFallbackValue”Summary
Section titled “Summary”registerFallbackValue is only needed when using the any() matcher from
package:mocktail for a custom type. Previously, registerFallbackValue was
needed for every Event and State when using MockBloc or MockCubit.
v8.x.x
class FakeMyEvent extends Fake implements MyEvent {}class FakeMyState extends Fake implements MyState {}class MyMockBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}
void main() { setUpAll(() { registerFallbackValue(FakeMyEvent()); registerFallbackValue(FakeMyState()); });
// Tests...}v9.0.0
class MyMockBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}
void main() { // Tests...}package:hydrated_bloc
Section titled “package:hydrated_bloc”❗✨ Introduce new HydratedBlocOverrides API
Section titled “❗✨ Introduce new HydratedBlocOverrides API”Rationale
Section titled “Rationale”Previously, a global singleton was used to override the Storage
implementation.
As a result, it was not possible to have multiple Storage implementations
scoped to different parts of the application. It was also more difficult to test
because of the shared global state across tests.
HydratedBloc v8.0.0 introduces a HydratedBlocOverrides class which allows
developers to override Storage for a specific Zone rather than relying on a
global mutable singleton.
v7.x.x
void main() async { HydratedBloc.storage = await HydratedStorage.build( storageDirectory: await getApplicationSupportDirectory(), );
// ...}v8.0.0
void main() { final storage = await HydratedStorage.build( storageDirectory: await getApplicationSupportDirectory(), );
HydratedBlocOverrides.runZoned( () { // ... }, storage: storage, );}HydratedBloc instances will use the Storage for the current Zone via
HydratedBlocOverrides.current.
This allows allow each Zone to function independently with its own
BlocOverrides.
v7.2.0
Section titled “v7.2.0”package:bloc
Section titled “package:bloc”✨ Introduce new on<Event> API
Section titled “✨ Introduce new on<Event> API”Rationale
Section titled “Rationale”The on<Event> API was introduced as part of
[Proposal] Replace mapEventToState with on<Event> in Bloc.
Due to an issue in Dart it’s
not always obvious what the value of state will be when dealing with nested
async generators (async*). Even though there are ways to work around the
issue, one of the core principles of the bloc library is to be predictable. The
on<Event> API was created to make the library as safe as possible to use and
to eliminate any uncertainty when it comes to state changes.
Summary
on<E> allows you to register an event handler for all events of type E. By
default, events will be processed concurrently when using on<E> as opposed to
mapEventToState which processes events sequentially.
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; } }}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)); }}If you want to retain the exact same behavior as in v7.1.0 you can register a
single event handler for all events and apply a sequential transformer:
import 'package:bloc/bloc.dart';import 'package:bloc_concurrency/bloc_concurrency.dart';
class MyBloc extends Bloc<MyEvent, MyState> { MyBloc() : super(MyState()) { on<MyEvent>(_onEvent, transformer: sequential()) }
FutureOr<void> _onEvent(MyEvent event, Emitter<MyState> emit) async { // TODO: logic goes here... }}You can also override the default EventTransformer for all blocs in your
application:
import 'package:bloc/bloc.dart';import 'package:bloc_concurrency/bloc_concurrency.dart';
void main() { Bloc.transformer = sequential<dynamic>(); ...}✨ Introduce new EventTransformer API
Section titled “✨ Introduce new EventTransformer API”Rationale
Section titled “Rationale”The on<Event> API opened the door to being able to provide a custom event
transformer per event handler. A new EventTransformer typedef was introduced
which enables developers to transform the incoming event stream for each event
handler rather than having to specify a single event transformer for all events.
Summary
An EventTransformer is responsible for taking the incoming stream of events
along with an EventMapper (your event handler) and returning a new stream of
events.
typedef EventTransformer<Event> = Stream<Event> Function(Stream<Event> events, EventMapper<Event> mapper)The default EventTransformer processes all events concurrently and looks
something like:
EventTransformer<E> concurrent<E>() { return (events, mapper) => events.flatMap(mapper);}v7.1.0
@overrideStream<Transition<MyEvent, MyState>> transformEvents(events, transitionFn) { return events .debounceTime(const Duration(milliseconds: 300)) .flatMap(transitionFn);}v7.2.0
/// Define a custom `EventTransformer`EventTransformer<MyEvent> debounce<MyEvent>(Duration duration) { return (events, mapper) => events.debounceTime(duration).flatMap(mapper);}
MyBloc() : super(MyState()) { /// Apply the custom `EventTransformer` to the `EventHandler` on<MyEvent>(_onEvent, transformer: debounce(const Duration(milliseconds: 300)))}⚠️ Deprecate transformTransitions API
Section titled “⚠️ Deprecate transformTransitions API”Rationale
Section titled “Rationale”The stream getter on Bloc makes it easy to override the outbound stream of
states therefore it’s no longer valuable to maintain a separate
transformTransitions API.
Summary
v7.1.0
@overrideStream<Transition<Event, State>> transformTransitions( Stream<Transition<Event, State>> transitions,) { return transitions.debounceTime(const Duration(milliseconds: 42));}v7.2.0
@overrideStream<State> get stream => super.stream.debounceTime(const Duration(milliseconds: 42));v7.0.0
Section titled “v7.0.0”package:bloc
Section titled “package:bloc”❗ Bloc and Cubit extend BlocBase
Section titled “❗ Bloc and Cubit extend BlocBase”Rationale
Section titled “Rationale”As a developer, the relationship between blocs and cubits was a bit awkward. When cubit was first introduced it began as the base class for blocs which made sense because it had a subset of the functionality and blocs would just extend Cubit and define additional APIs. This came with a few drawbacks:
-
All APIs would either have to be renamed to accept a cubit for accuracy or they would need to be kept as bloc for consistency even though hierarchically it is inaccurate (#1708, #1560).
-
Cubit would need to extend Stream and implement EventSink in order to have a common base which widgets like BlocBuilder, BlocListener, etc. can be implemented against (#1429).
Later, we experimented with inverting the relationship and making bloc the base class which partially resolved the first bullet above but introduced other issues:
- The cubit API is bloated due to the underlying bloc APIs like mapEventToState,
add, etc. (#2228)
- Developers can technically invoke these APIs and break things
- We still have the same issue of cubit exposing the entire stream API as before (#1429)
To address these issues we introduced a base class for both Bloc and Cubit
called BlocBase so that upstream components can still interoperate with both
bloc and cubit instances but without exposing the entire Stream and
EventSink API directly.
Summary
BlocObserver
v6.1.x
class SimpleBlocObserver extends BlocObserver { @override void onCreate(Cubit cubit) {...}
@override void onEvent(Bloc bloc, Object event) {...}
@override void onChange(Cubit cubit, Object event) {...}
@override void onTransition(Bloc bloc, Transition transition) {...}
@override void onError(Cubit cubit, Object error, StackTrace stackTrace) {...}
@override void onClose(Cubit cubit) {...}}v7.0.0
class SimpleBlocObserver extends BlocObserver { @override void onCreate(BlocBase bloc) {...}
@override void onEvent(Bloc bloc, Object event) {...}
@override void onChange(BlocBase bloc, Object? event) {...}
@override void onTransition(Bloc bloc, Transition transition) {...}
@override void onError(BlocBase bloc, Object error, StackTrace stackTrace) {...}
@override void onClose(BlocBase bloc) {...}}Bloc/Cubit
v6.1.x
final bloc = MyBloc();bloc.listen((state) {...});
final cubit = MyCubit();cubit.listen((state) {...});v7.0.0
final bloc = MyBloc();bloc.stream.listen((state) {...});
final cubit = MyCubit();cubit.stream.listen((state) {...});package:bloc_test
Section titled “package:bloc_test”❗seed returns a function to support dynamic values
Section titled “❗seed returns a function to support dynamic values”Rationale
Section titled “Rationale”In order to support having a mutable seed value which can be updated dynamically
in setUp, seed returns a function.
Summary
v7.x.x
blocTest( '...', seed: MyState(), ...);v8.0.0
blocTest( '...', seed: () => MyState(), ...);❗expect returns a function to support dynamic values and includes matcher support
Section titled “❗expect returns a function to support dynamic values and includes matcher support”Rationale
Section titled “Rationale”In order to support having a mutable expectation which can be updated
dynamically in setUp, expect returns a function. expect also supports
Matchers.
Summary
v7.x.x
blocTest( '...', expect: [MyStateA(), MyStateB()], ...);v8.0.0
blocTest( '...', expect: () => [MyStateA(), MyStateB()], ...);
// It can also be a `Matcher`blocTest( '...', expect: () => contains(MyStateA()), ...);❗errors returns a function to support dynamic values and includes matcher support
Section titled “❗errors returns a function to support dynamic values and includes matcher support”Rationale
Section titled “Rationale”In order to support having a mutable errors which can be updated dynamically in
setUp, errors returns a function. errors also supports Matchers.
Summary
v7.x.x
blocTest( '...', errors: [MyError()], ...);v8.0.0
blocTest( '...', errors: () => [MyError()], ...);
// It can also be a `Matcher`blocTest( '...', errors: () => contains(MyError()), ...);❗MockBloc and MockCubit
Section titled “❗MockBloc and MockCubit”Rationale
Section titled “Rationale”To support stubbing of various core APIs, MockBloc and MockCubit are
exported as part of the bloc_test package. Previously, MockBloc had to be
used for both Bloc and Cubit instances which was not intuitive.
Summary
v7.x.x
class MockMyBloc extends MockBloc<MyState> implements MyBloc {}class MockMyCubit extends MockBloc<MyState> implements MyBloc {}v8.0.0
class MockMyBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}class MockMyCubit extends MockCubit<MyState> implements MyCubit {}❗Mocktail Integration
Section titled “❗Mocktail Integration”Rationale
Section titled “Rationale”Due to various limitations of the null-safe
package:mockito described
here,
package:mocktail is used by MockBloc and
MockCubit. This allows developers to continue using a familiar mocking API
without the need to manually write stubs or rely on code generation.
Summary
v7.x.x
import 'package:mockito/mockito.dart';
...
when(bloc.state).thenReturn(MyState());verify(bloc.add(any)).called(1);v8.0.0
import 'package:mocktail/mocktail.dart';
...
when(() => bloc.state).thenReturn(MyState());verify(() => bloc.add(any())).called(1);Please refer to #347 as well as the mocktail documentation for more information.
package:flutter_bloc
Section titled “package:flutter_bloc”❗ rename cubit parameter to bloc
Section titled “❗ rename cubit parameter to bloc”Rationale
Section titled “Rationale”As a result of the refactor in package:bloc to introduce BlocBase which
Bloc and Cubit extend, the parameters of BlocBuilder, BlocConsumer, and
BlocListener were renamed from cubit to bloc because the widgets operate
on the BlocBase type. This also further aligns with the library name and
hopefully improves readability.
Summary
v6.1.x
BlocBuilder( cubit: myBloc, ...)
BlocListener( cubit: myBloc, ...)
BlocConsumer( cubit: myBloc, ...)v7.0.0
BlocBuilder( bloc: myBloc, ...)
BlocListener( bloc: myBloc, ...)
BlocConsumer( bloc: myBloc, ...)package:hydrated_bloc
Section titled “package:hydrated_bloc”❗storageDirectory is required when calling HydratedStorage.build
Section titled “❗storageDirectory is required when calling HydratedStorage.build”Rationale
Section titled “Rationale”In order to make package:hydrated_bloc a pure Dart package, the dependency on
package:path_provider was removed and
the storageDirectory parameter when calling HydratedStorage.build is
required and no longer defaults to getTemporaryDirectory.
Summary
v6.x.x
HydratedBloc.storage = await HydratedStorage.build();v7.0.0
import 'package:path_provider/path_provider.dart';
...
HydratedBloc.storage = await HydratedStorage.build( storageDirectory: await getTemporaryDirectory(),);v6.1.0
Section titled “v6.1.0”package:flutter_bloc
Section titled “package:flutter_bloc”❗context.bloc and context.repository are deprecated in favor of context.read and context.watch
Section titled “❗context.bloc and context.repository are deprecated in favor of context.read and context.watch”Rationale
Section titled “Rationale”context.read, context.watch, and context.select were added to align with
the existing provider API which many
developers are familiar and to address issues that have been raised by the
community. To improve the safety of the code and maintain consistency,
context.bloc was deprecated because it can be replaced with either
context.read or context.watch dependending on if it’s used directly within
build.
context.watch
context.watch addresses the request to have a
MultiBlocBuilder because we can
watch several blocs within a single Builder in order to render UI based on
multiple states:
Builder( builder: (context) { final stateA = context.watch<BlocA>().state; final stateB = context.watch<BlocB>().state; final stateC = context.watch<BlocC>().state;
// return a Widget which depends on the state of BlocA, BlocB, and BlocC });context.select
context.select allows developers to render/update UI based on a part of a bloc
state and addresses the request to have a
simpler buildWhen.
final name = context.select((UserBloc bloc) => bloc.state.user.name);The above snippet allows us to access and rebuild the widget only when the current user’s name changes.
context.read
Even though it looks like context.read is identical to context.bloc there
are some subtle but significant differences. Both allow you to access a bloc
with a BuildContext and do not result in rebuilds; however, context.read
cannot be called directly within a build method. There are two main reasons to
use context.bloc within build:
- To access the bloc’s state
@overrideWidget build(BuildContext context) { final state = context.bloc<MyBloc>().state; return Text('$state');}The above usage is error prone because the Text widget will not be rebuilt if
the state of the bloc changes. In this scenario, either a BlocBuilder or
context.watch should be used.
@overrideWidget build(BuildContext context) { final state = context.watch<MyBloc>().state; return Text('$state');}or
@overrideWidget build(BuildContext context) { return BlocBuilder<MyBloc, MyState>( builder: (context, state) => Text('$state'), );}- To access the bloc so that an event can be added
@overrideWidget build(BuildContext context) { final bloc = context.bloc<MyBloc>(); return ElevatedButton( onPressed: () => bloc.add(MyEvent()), ... )}The above usage is inefficient because it results in a bloc lookup on each
rebuild when the bloc is only needed when the user taps the ElevatedButton. In
this scenario, prefer to use context.read to access the bloc directly where it
is needed (in this case, in the onPressed callback).
@overrideWidget build(BuildContext context) { return ElevatedButton( onPressed: () => context.read<MyBloc>().add(MyEvent()), ... )}Summary
v6.0.x
@overrideWidget build(BuildContext context) { final bloc = context.bloc<MyBloc>(); return ElevatedButton( onPressed: () => bloc.add(MyEvent()), ... )}v6.1.x
@overrideWidget build(BuildContext context) { return ElevatedButton( onPressed: () => context.read<MyBloc>().add(MyEvent()), ... )}?> If accessing a bloc to add an event, perform the bloc access using
context.read in the callback where it is needed.
v6.0.x
@overrideWidget build(BuildContext context) { final state = context.bloc<MyBloc>().state; return Text('$state');}v6.1.x
@overrideWidget build(BuildContext context) { final state = context.watch<MyBloc>().state; return Text('$state');}?> Use context.watch when accessing the state of the bloc in order to ensure
the widget is rebuilt when the state changes.
v6.0.0
Section titled “v6.0.0”package:bloc
Section titled “package:bloc”❗BlocObserver onError takes Cubit
Section titled “❗BlocObserver onError takes Cubit”Rationale
Section titled “Rationale”Due to the integration of Cubit, onError is now shared between both Bloc
and Cubit instances. Since Cubit is the base, BlocObserver will accept a
Cubit type rather than a Bloc type in the onError override.
v5.x.x
class MyBlocObserver extends BlocObserver { @override void onError(Bloc bloc, Object error, StackTrace stackTrace) { super.onError(bloc, error, stackTrace); }}v6.0.0
class MyBlocObserver extends BlocObserver { @override void onError(Cubit cubit, Object error, StackTrace stackTrace) { super.onError(cubit, error, stackTrace); }}❗Bloc does not emit last state on subscription
Section titled “❗Bloc does not emit last state on subscription”Rationale
Section titled “Rationale”This change was made to align Bloc and Cubit with the built-in Stream
behavior in Dart. In addition, conforming this the old behavior in the context
of Cubit led to many unintended side-effects and overall complicated the
internal implementations of other packages such as flutter_bloc and
bloc_test unnecessarily (requiring skip(1), etc…).
v5.x.x
final bloc = MyBloc();bloc.listen(print);Previously, the above snippet would output the initial state of the bloc followed by subsequent state changes.
v6.x.x
In v6.0.0, the above snippet does not output the initial state and only outputs subsequent state changes. The previous behavior can be achieved with the following:
final bloc = MyBloc();print(bloc.state);bloc.listen(print);?> Note: This change will only affect code that relies on direct bloc
subscriptions. When using BlocBuilder, BlocListener, or BlocConsumer there
will be no noticeable change in behavior.
package:bloc_test
Section titled “package:bloc_test”❗MockBloc only requires State type
Section titled “❗MockBloc only requires State type”Rationale
Section titled “Rationale”It is not necessary and eliminates extra code while also making MockBloc
compatible with Cubit.
v5.x.x
class MockCounterBloc extends MockBloc<CounterEvent, int> implements CounterBloc {}v6.0.0
class MockCounterBloc extends MockBloc<int> implements CounterBloc {}❗whenListen only requires State type
Section titled “❗whenListen only requires State type”Rationale
Section titled “Rationale”It is not necessary and eliminates extra code while also making whenListen
compatible with Cubit.
v5.x.x
whenListen<CounterEvent,int>(bloc, Stream.fromIterable([0, 1, 2, 3]));v6.0.0
whenListen<int>(bloc, Stream.fromIterable([0, 1, 2, 3]));❗blocTest does not require Event type
Section titled “❗blocTest does not require Event type”Rationale
Section titled “Rationale”It is not necessary and eliminates extra code while also making blocTest
compatible with Cubit.
v5.x.x
blocTest<CounterBloc, CounterEvent, int>( 'emits [1] when increment is called', build: () async => CounterBloc(), act: (bloc) => bloc.add(CounterEvent.increment), expect: const <int>[1],);v6.0.0
blocTest<CounterBloc, int>( 'emits [1] when increment is called', build: () => CounterBloc(), act: (bloc) => bloc.add(CounterEvent.increment), expect: const <int>[1],);❗blocTest skip defaults to 0
Section titled “❗blocTest skip defaults to 0”Rationale
Section titled “Rationale”Since bloc and cubit instances will no longer emit the latest state for new
subscriptions, it was no longer necessary to default skip to 1.
v5.x.x
blocTest<CounterBloc, CounterEvent, int>( 'emits [0] when skip is 0', build: () async => CounterBloc(), skip: 0, expect: const <int>[0],);v6.0.0
blocTest<CounterBloc, int>( 'emits [] when skip is 0', build: () => CounterBloc(), skip: 0, expect: const <int>[],);The initial state of a bloc or cubit can be tested with the following:
test('initial state is correct', () { expect(MyBloc().state, InitialState());});❗blocTest make build synchronous
Section titled “❗blocTest make build synchronous”Rationale
Section titled “Rationale”Previously, build was made async so that various preparation could be done
to put the bloc under test in a specific state. It is no longer necessary and
also resolves several issues due to the added latency between the build and the
subscription internally. Instead of doing async prep to get a bloc in a desired
state we can now set the bloc state by chaining emit with the desired state.
v5.x.x
blocTest<CounterBloc, CounterEvent, int>( 'emits [2] when increment is added', build: () async { final bloc = CounterBloc(); bloc.add(CounterEvent.increment); await bloc.take(2); return bloc; } act: (bloc) => bloc.add(CounterEvent.increment), expect: const <int>[2],);v6.0.0
blocTest<CounterBloc, int>( 'emits [2] when increment is added', build: () => CounterBloc()..emit(1), act: (bloc) => bloc.add(CounterEvent.increment), expect: const <int>[2],);package:flutter_bloc
Section titled “package:flutter_bloc”❗BlocBuilder bloc parameter renamed to cubit
Section titled “❗BlocBuilder bloc parameter renamed to cubit”Rationale
Section titled “Rationale”In order to make BlocBuilder interoperate with bloc and cubit instances
the bloc parameter was renamed to cubit (since Cubit is the base class).
v5.x.x
BlocBuilder( bloc: myBloc, builder: (context, state) {...})v6.0.0
BlocBuilder( cubit: myBloc, builder: (context, state) {...})❗BlocListener bloc parameter renamed to cubit
Section titled “❗BlocListener bloc parameter renamed to cubit”Rationale
Section titled “Rationale”In order to make BlocListener interoperate with bloc and cubit instances
the bloc parameter was renamed to cubit (since Cubit is the base class).
v5.x.x
BlocListener( bloc: myBloc, listener: (context, state) {...})v6.0.0
BlocListener( cubit: myBloc, listener: (context, state) {...})❗BlocConsumer bloc parameter renamed to cubit
Section titled “❗BlocConsumer bloc parameter renamed to cubit”Rationale
Section titled “Rationale”In order to make BlocConsumer interoperate with bloc and cubit instances
the bloc parameter was renamed to cubit (since Cubit is the base class).
v5.x.x
BlocConsumer( bloc: myBloc, listener: (context, state) {...}, builder: (context, state) {...})v6.0.0
BlocConsumer( cubit: myBloc, listener: (context, state) {...}, builder: (context, state) {...})v5.0.0
Section titled “v5.0.0”package:bloc
Section titled “package:bloc”❗initialState has been removed
Section titled “❗initialState has been removed”Rationale
Section titled “Rationale”As a developer, having to override initialState when creating a bloc presents
two main issues:
- The
initialStateof the bloc can be dynamic and can also be referenced at a later point in time (even outside of the bloc itself). In some ways, this can be viewed as leaking internal bloc information to the UI layer. - It’s verbose.
v4.x.x
class CounterBloc extends Bloc<CounterEvent, int> { @override int get initialState => 0;
...}v5.0.0
class CounterBloc extends Bloc<CounterEvent, int> { CounterBloc() : super(0);
...}?> For more information check out #1304
❗BlocDelegate renamed to BlocObserver
Section titled “❗BlocDelegate renamed to BlocObserver”Rationale
Section titled “Rationale”The name BlocDelegate was not an accurate description of the role that the
class played. BlocDelegate suggests that the class plays an active role
whereas in reality the intended role of the BlocDelegate was for it to be a
passive component which simply observes all blocs in an application.
v4.x.x
class MyBlocDelegate extends BlocDelegate { ...}v5.0.0
class MyBlocObserver extends BlocObserver { ...}❗BlocSupervisor has been removed
Section titled “❗BlocSupervisor has been removed”Rationale
Section titled “Rationale”BlocSupervisor was yet another component that developers had to know about and
interact with for the sole purpose of specifying a custom BlocDelegate. With
the change to BlocObserver we felt it improved the developer experience to set
the observer directly on the bloc itself.
?> This changed also enabled us to decouple other bloc add-ons like
HydratedStorage from the BlocObserver.
v4.x.x
BlocSupervisor.delegate = MyBlocDelegate();v5.0.0
Bloc.observer = MyBlocObserver();package:flutter_bloc
Section titled “package:flutter_bloc”❗BlocBuilder condition renamed to buildWhen
Section titled “❗BlocBuilder condition renamed to buildWhen”Rationale
Section titled “Rationale”When using BlocBuilder, we previously could specify a condition to determine
whether the builder should rebuild.
BlocBuilder<MyBloc, MyState>( condition: (previous, current) { // return true/false to determine whether to call builder }, builder: (context, state) {...})The name condition is not very self-explanatory or obvious and more
importantly, when interacting with a BlocConsumer the API became inconsistent
because developers can provide two conditions (one for builder and one for
listener). As a result, the BlocConsumer API exposed a buildWhen and
listenWhen
BlocConsumer<MyBloc, MyState>( listenWhen: (previous, current) { // return true/false to determine whether to call listener }, listener: (context, state) {...}, buildWhen: (previous, current) { // return true/false to determine whether to call builder }, builder: (context, state) {...},)In order to align the API and provide a more consistent developer experience,
condition was renamed to buildWhen.
v4.x.x
BlocBuilder<MyBloc, MyState>( condition: (previous, current) { // return true/false to determine whether to call builder }, builder: (context, state) {...})v5.0.0
BlocBuilder<MyBloc, MyState>( buildWhen: (previous, current) { // return true/false to determine whether to call builder }, builder: (context, state) {...})❗BlocListener condition renamed to listenWhen
Section titled “❗BlocListener condition renamed to listenWhen”Rationale
Section titled “Rationale”For the same reasons as described above, the BlocListener condition was also
renamed.
v4.x.x
BlocListener<MyBloc, MyState>( condition: (previous, current) { // return true/false to determine whether to call listener }, listener: (context, state) {...})v5.0.0
BlocListener<MyBloc, MyState>( listenWhen: (previous, current) { // return true/false to determine whether to call listener }, listener: (context, state) {...})package:hydrated_bloc
Section titled “package:hydrated_bloc”❗HydratedStorage and HydratedBlocStorage renamed
Section titled “❗HydratedStorage and HydratedBlocStorage renamed”Rationale
Section titled “Rationale”In order to improve code reuse between
hydrated_bloc and
hydrated_cubit, the concrete default
storage implementation was renamed from HydratedBlocStorage to
HydratedStorage. In addition, the HydratedStorage interface was renamed from
HydratedStorage to Storage.
v4.0.0
class MyHydratedStorage implements HydratedStorage { ...}v5.0.0
class MyHydratedStorage implements Storage { ...}❗HydratedStorage decoupled from BlocDelegate
Section titled “❗HydratedStorage decoupled from BlocDelegate”Rationale
Section titled “Rationale”As mentioned earlier, BlocDelegate was renamed to BlocObserver and was set
directly as part of the bloc via:
Bloc.observer = MyBlocObserver();The following change was made to:
- Stay consistent with the new bloc observer API
- Keep the storage scoped to just
HydratedBloc - Decouple the
BlocObserverfromStorage
v4.0.0
BlocSupervisor.delegate = await HydratedBlocDelegate.build();v5.0.0
HydratedBloc.storage = await HydratedStorage.build();❗Simplified Initialization
Section titled “❗Simplified Initialization”Rationale
Section titled “Rationale”Previously, developers had to manually call
super.initialState ?? DefaultInitialState() in order to setup their
HydratedBloc instances. This is clunky and verbose and also incompatible with
the breaking changes to initialState in bloc. As a result, in v5.0.0
HydratedBloc initialization is identical to normal Bloc initialization.
v4.0.0
class CounterBloc extends HydratedBloc<CounterEvent, int> { @override int get initialState => super.initialState ?? 0;}v5.0.0
class CounterBloc extends HydratedBloc<CounterEvent, int> { CounterBloc() : super(0);
...}