I am a bit confused about the new release of Bloc: 6.0.0, adding Cubit notion, is the bloc depreciated or we can use both of them?
Cubit is a subset of the BLoC Pattern package that does not rely on events and instead uses methods to emit new states.
So, we can use Cubit for simple states, and as needed we can use the Bloc.
UPDATE : additional comparison
There are many advantages of choosing Cubit over Bloc. The two main benefits are:
Cubit is a subset of Bloc; so, it reduces complexity. Cubit eliminates the event classes. Cubit uses emit rather than yield to emit state. Since emit works synchronously, you can ensure that the state is updated in the next line.
more details: bloclibrary
Cubit is perfectly suitable to any app scale and anyone saying one scales better than the other is ill-informed. Software architecture is rarely about absolutes, rather it is about pros and cons, and more often than not people claiming you should use X don't really understand the implications.
Bloc is essentially event driven architecture while cubit is vanilla code. This has the same implications that any event driven architecture has.
In short: Event driven or not
// this is normal / vanilla code, like cubits
myFuction(); // i'm just calling my function here, nothing magical going on
myFunction() {
print('running my function');
}
// this is event driven, like blocs
eventEmitter.emit(SomeEvent()); // emitting an event, i don't know if it will be handled.
on(SomeEvent).exectute(myFunction);
// somewhere else in your code base...
on(SomeEvent).exectute(myAnalyticsFunction);
myFunction() {
print('my function');
}
myAnalyticsFunction() {
print('my analytics function');
}
The code above demonstrate the difference between vanilla execution vs an event driven one. As you can see event driven architectures comes with its own drawbacks:
- adds complexity
- adds indirection, you do not call the function directly
- you cannot add a breakpoint to flow through code !
- you cannot know which handler will execute after an event is emitted just by looking at the source of emission (you have to do a search)
- you don't know in which order an event will be processed
It adds the following advantage:
- allow you to decouple the emitting site from the processing sites
- allows you to add event handlers at will without changing the existing code.
- allows you to easily add cross cutting concerns (logging, analytics..) by just subscribing to all events.
Any time you use an event driven architecture, you lose some of the benefits of traditional code and this should be used with care: when needed.
One advantage of event driven architectures is that events can be used by 0 or many handlers, the emitter does not care. The disadvantage is the indirection and extra boiler plate.
Bloc vs Cubit
Traceability
When you use a bloc you have a Transition
that contains the event:
Transition {
currentState: AuthenticationState.authenticated,
event: LogoutRequested,
nextState: AuthenticationState.unauthenticated
}
When you use cubit it does not
Transition {
currentState: AuthenticationState.authenticated,
nextState: AuthenticationState.unauthenticated
}
One advantage is that you gain some traceability because you know which event triggered the changes just by looking at the logs. You see eventA happened then a change of state happened.
In practice, for the cubit you can often infer traceability from the changes themselves without the "label", because there is not many action that can output this change.
One disadvantage is indirection cost which shouldn't be understated, you cannot put a debugger and follow the code here by definition.
Event sharing
In essence, emitting an Action/Event that is then mapped to call a function, is just like calling the function directly. The only fundamental change is when an action must be consumed by multiple consumers, blocs or others (example analytics). If an action must be shared between blocs (example reset data on logout), then blocs can be handy in that area. However there are other ways to achieve that without requiring your whole code base to use bloc...
Cross cutting concerns
One aspect where, I think, event driven architecture shine is when you have to add cross cutting concerns that you want to apply to a wide range of events. For instance if you want to pipe all events to google analytics and log them in two lines of code, this would be easily done if you already had bloc. However even this doesn't require you to go with bloc, it just require an event to be emitted at some point, which would be a better pattern.
Conclusion
If you ask yourself should I use bloc or cubit, go with cubit. Once you have a clearly defined reason to use an event emitter of some sort, you can add it.
I don't agree with opinions that you can only use Cubit only for simple cases, or for small apps. I would even say that Cubit can handle pretty complex cases without the need to be converted into a Bloc.
The main advantage of Cubit is that it has less boilerplate code, and has a straightforward and synchronous way for emitting states (as there is no Event class, but a simple functions instead). In most cases you won't need a Bloc, so you can easily use Cubits if you don't have to transform your events. In those rare cases when you need to transform events, you can easily refactor Cubit into a Bloc, as both of them extends BlocBase
class.
The main difference between Cubit and Bloc is that in Bloc you have Event class in addition to State. So, you are able to use EventTransformer
function in order to manipulate your events. For example, you can add debounce or throttle to your event. Or even have some complicated event stream mapping. That's the main benefit of using a Bloc instead of a Cubit.
An example of using EventTransformer
for debouncing event:
import 'package:stream_transform/stream_transform.dart';
EventTransformer<Event> _debounce<Event>(Duration duration) {
return (events, mapper) => events.debounce(duration).switchMap(mapper);
}
Usage of the _debounce
in event mapping:
class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
const ExampleBloc()
: super(const ExampleState()) {
on<ExampleEvent>(
_onExampleEvent,
transformer: _debounce(Duration(seconds: 1)),
);
}
...
}
Basically, that's the core difference between Cubit and Bloc.
P.S. Usage of Blocs / Cubits in projects is also quite opinionated, and some teams might use Blocs only due to code consistency.
My rule is to use Cubit for simple State management where you just have one kind of event to bind to the state. And use Bloc for complex state management where you can have many events to map to states.
For example let consider below Cubit
class SimpleCubit extends Cubit<SimpleState> {
SimpleCubit () : super(InitialState());
void updateState(String stateName){
emit(NewState(stateName));
}
}
Let now have a look on Bloc
class SimpleBloc extends Bloc<SimpleEvent, SimpleState> {
SimpleBloc() : super(InitialState()){
on<SimpleEven1>(_handleEvent1);
on<SimpleEven2>(_handleEvent2)
}
Future<void> _handleEvent1(SimpleEvent event, Emitter<SimpleState1> emit) async {
// Put your code here
emit(SimpleState1(event.args))
}
Future<void> _handleEvent2(SimpleEvent event, Emitter<SimpleState2> emit) async {
// Put your code here
emit(SimpleState2(event.args))
}
}
Bloc forces you to map each event to a separate function which is good when you have a complex state. Although with some extra codes you can achieve the same with Cubit, I prefer Bloc in that given case.
© 2022 - 2024 — McMap. All rights reserved.