Migration Guide
이 내용은 아직 번역본이 없습니다.
Refer to the rationale for reintroducing the Bloc.observer and Bloc.transformer overrides.
v8.x.x
v9.0.0
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:
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
v8.1.0
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
BlocObserver
orEventTransformer
implementations scoped to different parts of the application - Have
BlocObserver
orEventTransformer
overrides be scoped to a package- If a package were to depend on
package:bloc
and registered its ownBlocObserver
, any consumer of the package would either have to overwrite the package’sBlocObserver
or 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
v8.0.0
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
.
The goal of these changes is:
- make internal unhandled exceptions extremely obvious while still preserving bloc functionality
- support
addError
without 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).
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
v8.0.0
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
.
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
.
mapEventToState
removed in favor ofon<Event>
transformEvents
removed in favor ofEventTransformer
APITransitionFunction
typedef removed in favor ofEventTransformer
APIlisten
removed in favor ofstream.listen
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
v9.0.0
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
v8.0.0
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
.
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
v7.2.0
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:
You can also override the default EventTransformer
for all blocs in your application:
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.
The default EventTransformer
processes all events concurrently and looks something like:
v7.1.0
v7.2.0
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
v7.2.0
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
v7.0.0
Bloc/Cubit
v6.1.x
v7.0.0
In order to support having a mutable seed value which can be updated dynamically in setUp
, seed
returns a function.
Summary
v7.x.x
v8.0.0
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
v8.0.0
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
v8.0.0
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
v8.0.0
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
v8.0.0
Please refer to #347 as well as the mocktail documentation for more information.
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
v7.0.0
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
v7.0.0
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:
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.
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
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.
or
- To access the bloc so that an event can be added
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).
Summary
v6.0.x
v6.1.x
?> 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
v6.1.x
?> Use context.watch
when accessing the state of the bloc in order to ensure the widget is rebuilt when the state changes.
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
v6.0.0
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
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:
?> 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.
It is not necessary and eliminates extra code while also making MockBloc
compatible with Cubit
.
v5.x.x
v6.0.0
It is not necessary and eliminates extra code while also making whenListen
compatible with Cubit
.
v5.x.x
v6.0.0
It is not necessary and eliminates extra code while also making blocTest
compatible with Cubit
.
v5.x.x
v6.0.0
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
v6.0.0
The initial state of a bloc or cubit can be tested with the following:
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
v6.0.0
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
v6.0.0
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
v6.0.0
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
v6.0.0
As a developer, having to override initialState
when creating a bloc presents two main issues:
- The
initialState
of 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
v5.0.0
?> For more information check out #1304
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
v5.0.0
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
v5.0.0
When using BlocBuilder
, we previously could specify a condition
to determine whether the builder
should rebuild.
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
In order to align the API and provide a more consistent developer experience, condition
was renamed to buildWhen
.
v4.x.x
v5.0.0
For the same reasons as described above, the BlocListener
condition was also renamed.
v4.x.x
v5.0.0
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
v5.0.0
As mentioned earlier, BlocDelegate
was renamed to BlocObserver
and was set directly as part of the bloc
via:
The following change was made to:
- Stay consistent with the new bloc observer API
- Keep the storage scoped to just
HydratedBloc
- Decouple the
BlocObserver
fromStorage
v4.0.0
v5.0.0
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
v5.0.0