Modern implementation of the Original BLoC
This package contains a modern (bloc
package version 8.0.0+) implementation of the Original, Stream/generator-based BLoC with several modifications and convenience extras.
After the 7.2.0 version update, the bloc has changed. Generators and the signature method mapEventToState
were replaced with method-based pattern-matching using the on
method and its argument handler. This approach is defiantly usable and solves a particular bug of the Dart language itself, but it lacks a few things.
- Power of streams. They can be transformed natively using a vast choice of transformers, but with the new bloc, streams are hidden under the hood and are not "first-class citizens".
- Power of generators. They allow to asynchronously return multiple values, including other streams. The new version emulates their behavior using higher-order functions.
- New
on
approach makesfreezed
basically useless. It is possible to register just a single handler, but it negates the whole point of theon
handlers.
This package brings back the Original bloc with all the benefits, whilst maintaining 100% compatibility with bloc
and flutter_bloc
packages. The StreamBloc
s can be used with all flutter_bloc
widgets; they implement the same interfaces.
If you are familiar with the bloc before the 8.0.0/7.2.0 you are familiar with StreamBloc
– the central class of this package. Documentation for previous bloc's versions can be used for this package besides a few modifications that are listed in the next section.
StreamBloc
uses a central event-processing method called mapEventToStates
to convert a single Event to a Stream of States that are emitted asynchronously. Official Bloc
can be directly translated to StreamBloc
as described below.
It is highly advised to use freezed package. The following example is a demonstration and should not be considered a "Best practice" for StreamBloc
abstract class CounterEvent {} // Shared counter event type
class Increment implements CounterEvent {} // Increment counter event
class Decrement implements CounterEvent {} // Decrement counter event
class OnCounterBloc extends Bloc<CounterEvent, int> { // Official Bloc – `on`s
OnCounterBloc() : super(0) {
on<Increment>((event, emit) => emit(state + 1));
on<Decrement>((event, emit) => emit(state - 1));
}
}
class StreamCounterBloc extends StreamBloc<CounterEvent, int> { // StreamBloc – `mapEventToStates`
StreamCounterBloc() : super(0);
@override
Stream<int> mapEventToStates(CounterEvent event) async* {
if (event is Increment) {
yield state + 1;
} else if (event is Decrement) {
yield state - 1;
}
}
}
There are five main differences from the Original bloc.
-
mapEventToState
is renamed tomapEventToStates
. The method returns an asynchronous sequence of states – not a single state. -
StreamBloc
's type parameters/generics are constrained to subclasses of anObject?
. -
Bloc can emit identical states consequentially. The output stream of the
StreamBloc
is not distinct because of two main reasonsflutter_bloc
'sBlocListener
/BlocConsumer
may be interested in any new emitted state, even if the state had not changedstream.map(...)
/stream.where(...)
(essentiallyBlocBuilder
and/orBlocSelector
) applied tostream.distinct()
removes the guarantee of uniques event in the stream, making thedistinct
redundant; it should be applied last, not first.
-
Bloc Observer can be injected both through zone injection and static variable with the specified priority.
-
Bloc Transformers is extended with an additional method that transforms source events and is applied before the events-transitions transformer.
The package also offers a single convenience mixin BlocLifecycleMixin
which makes Bloc-to-Bloc communications easier. It offers two methods: listenToStream
and listenToStreamable
. Below is an example of its usage.
class IncrementEvent {
final int amount;
const IncrementEvent(this.amount);
}
class IncrementBloc extends StreamBloc<IncrementEvent, int>
with BlocLifecycleMixin {
IncrementBloc() : super(0) {
reactToStream<int>(
Stream.periodic(const Duration(seconds: 1), (i) => i),
(passed) => IncrementEvent(passed),
);
listenToStream<int>(
stream,
print,
);
}
@override
Stream<int> mapEventToStates(IncrementEvent event) => Stream.value(
state + event.amount,
);
}
All methods return an instance of StreamSubscription
which can be canceled by hand if desired, but it is optional – it will be canceled any way on the closing of a Bloc that mixes in BlocLifecycleMixin