Modern implementation of the Original BLoC
stream_bloc
Modern implementation of the Original BLoC
About
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.
Motivation
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 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.
Overview
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 packages 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;
}
}
}
Modifications
There are three main differences from the Original bloc.
-
mapEventToState
is renamed tomapEventToStates
. The method returns an asynchronous sequence of states – not a single state. -
It is not possible to emit a new state without it being a response to a certain event.
StreamBloc
does not implementEmittable
and does not have anemit
method. The original bloc has its method because bothCubit
andBloc
are descendants of the same base class, butemit
should not be used within aBloc
. It is marked as visible for testing, but it is always a good idea to test a whole instead of its parts. -
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.
Extras
The package also offers a single convenience mixin BlocLifecycleMixin
which makes Bloc-to-Bloc communications easier. It offers four methods: listenToStream
, listenToStreamable
, reactToStream
and reactToStreamable
. Below is an example of its usage.
enum EventA { eventA }
class BlocA extends StreamBloc<EventA, int> {
BlocA() : super(0);
@override
Stream<int> mapEventToStates(EventA event) => Stream.value(1);
}
enum EventB { eventB }
class BlocB extends StreamBloc<EventB, int> with BlocLifecycleMixin<EventB> {
BlocB(BlocA blocA) : super(0) {
/// Will print every new state of this Bloc to the console.
listenToStream(stream, print);
/// Will add [EventB.eventB] to this bloc every time BlocA emits any state.
reactToStreamable<int>(blocA, (blocAState) => EventB.eventB);
}
@override
Stream<int> mapEventToStates(EventB event) => Stream.value(1);
}
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