-
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Proposal] Replace mapEventToState
with on<Event>
in Bloc
#2526
Comments
I Like this one <3 |
With the understanding that this will not impact everything that's already in place, meaning this change will not require a refactor in the presentation or the domain layer, this is epic. To me, simplicity is elegance and this makes a lot of sense ! Instead of @override
Stream<int> mapEventToState(CounterEvent event) async* {
if (event is Increment) yield* _mapIncrementToState(event);
}
Stream<int> _mapIncrementToState(Increment event) async* {
yield state + 1;
} we have on<Increment>((event, emit) => emit(state + 1)); And to me that is infinitely more elegant |
That was definitely the biggest pain point while learning Dart, Flutter, and Bloc at the same time. I would often get confused as to when to use
One of the cool things about bloc is how native it felt by encouraging the use of Dart's built in generator features (despite the learning curve). I still think this is a good change, though, given how much it reduces the amount of boilerplate needed and the fact that Dart still hasn't released the fix for such a critical bug.
That is actually the biggest benefit of adopting this approach, to me. It feels much more declarative, cleaner, and seems to fit nicely within bloc's approach to state management.
That cleaned up nicely 😮 |
This looks clean and awesome. Just a doubt, will this be creating a subscription or listener kinda thing when the object of bloc is created?
as there won't be any method now. |
Thanks for the feedback! It shouldn't create any additional subscriptions -- it'll just register the handler for that event type and under the hood the number of subscriptions should stay the same. Hope that helps 👍 |
That was pretty helpful and easy to understand |
Really nice and semantic refactoring! 🚀 Any way to unregister or update an event handler? 🤔 |
What would an example that includes some basic async processing look like? |
Thanks! I thought about having |
It could look something like: abstract class WeatherEvent {}
class WeatherRequested extends WeatherEvent {
WeatherRequested(this.latLng);
final LatLng latLng;
}
abstract class WeatherState {}
class WeatherInitial extends WeatherState {}
class WeatherLoadInProgress extends WeatherState {}
class WeatherLoadFailure extends WeatherState {}
class WeatherLoadSuccess extends WeatherState {
WeatherLoadSuccess(this.weather);
final Weather weather;
}
class WeatherBloc extends Bloc<WeatherEvent, WeatherState> {
WeatherBloc(this.weatherRepository) : super(WeatherInitial()) {
on<WeatherRequested>(_onWeatherRequested);
}
final WeatherRepository weatherRepository;
void _onWeatherRequested(WeatherRequested event, Emit<WeatherState> emit) async {
emit(WeatherLoadInProgress());
try {
final weather = await weatherRepository.getWeather(event.latLng);
emit(WeatherLoadSuccess(weather);
} catch (_) {
emit(WeatherLoadFailure());
}
}
} Hope that helps 👍 |
Gotcha, so the idea is that |
Yeah |
Amazing update and a pure example on how minimalism, even though it's tough to achieve, can be such a fresh breath of air for bloc and not only that! Thanks, @felangel! |
@felangel It is very clean, I would like to see a similar way to the Freezed library
|
Thanks! You should still be able to use class MyBloc extends Bloc<MyEvent, MyState> {
MyBloc() : super(MyState.initial()) {
on<MyEvent>(_onEvent);
}
void _onEvent(MyEvent event, Emit<MyState> emit) async {
event.when(
a: () => emit(state.copyWith(...)),
b: () => emit(state.copyWith(...)),
...
}
} |
@felangel I mean merge freezed magic to your magic 😄 , no problem we can still use it separately |
This comment will add zero value to the proposal (which, by the way, amazing job on making the message clear!)... but I can't wait to see sealed classes/unions and data classes baked into Dart! These API changes + those language changes will be 💯! |
Nice. That will make the code less boilerplate and more readable at the same time for bloc. This will be like reading plain English! on<Increment>(_onIncrement);
on<Decrement>(_onDecrement); But I see that the cubit code is changed to a bloc by having an event instead of public methods
So how is that not going to break cubits?
Thanks! Sorry for the confusion, the cubit code will not change at all. I was just trying to show how these changes would make it easier to convert a cubit into a bloc at a later time if you decided you wanted to switch. Let me know if that helps 👍 |
Understood 👍 |
That's great 👌 |
Thanks! typedef Emit<State> = void Function(State); |
I like this! Mainly for reasons that others have already explained many times over now ☝️ But I have a question—how would the library behave when multiple handlers are defined for the same event type? Would both be called (and in what order), or would one override the other? Would that be prevented somehow (I'm struggling to imagine how that could be done AOT)? abstract class CounterEvent {}
class Increment extends CounterEvent {}
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<Increment>((event, emit) => emit(state + 1));
on<Increment>((event, emit) => emit(state - 1));
}
} This is the only thing I can come up with that I think represents something less intuitive about this approach than having one Anyway, I still see this is an improvement and would be excited to work with this API—just curious how you'd plan to approach that! |
You should be able to do: on<MyEvent>((event, emit) {
return emit.onEach(
state.map(
init: (_) => event.maybeMap(
load: (_) => _load(),
orElse: () => Stream.empty(),
),
data: (_) => event.maybeMap(
reload: (_) => _reload(),
orElse: () => Stream.empty(),
),
),
onData: (state) {
if (state != null) emit(state);
},
);
}); Hope that helps 👍 |
@felangel : Thanks for a great library! I know this change proposal is already implemented, but could we get a clarification of the following statement from the migration guide:
Example:
Does it mean that if bloc receives Is the fact that events are processed concurrently also valid for a single handler ? In previous example, if user feeds the bloc Last question - the migration states "By default, events will be processed concurrently" - so is there a way to actually force a bloc to handle events sequentially ? (which I assume is the case with mapEventToState - at least that is the behaviour we are expecting in two of our apps that are in the app-store and working without issues). |
@matekdk thanks for the support, I really appreciate it!
In that example, the emitted states will be: [state(1), state(3), state(2)] 👍
Yup by default each event handler processes events concurrently and multiple registered handlers function independently.
Yes, that's correct. If that's undesirable you can apply a transformer like
Yes, I updated the migration docs to hopefully make this more explicit but if you want to retain the previous behavior with the new API you can just register one event handler for all events and apply a 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 import 'package:bloc/bloc.dart';
import 'package:bloc_concurrency/bloc_concurrency.dart';
void main() {
Bloc.transformer = sequential<dynamic>();
...
} Hope that helps! |
how can I migrate this?
|
You could do: class MyBloc extends Bloc<MyEvent, MyState> {
MyBloc() : super(MyState.initial()) {
on<MyEvent>(_onEvent);
}
Future<void> _onEvent(MyEvent event, Emit<MyState> emit) async {
await event.map(
authCheckRequested: (e) async {
final userOption = await _authFacade.getSignedInUserId();
emit(userOption.fold(
() => const AuthState.unauthenticated(),
(_) => const AuthState.authenticated(),
));
},
signedOut: (e) async {
await _authFacade.signOut();
emit(const AuthState.unauthenticated());
},
...
}
} Hope that helps 👍 |
Hi, after migrating to v7.2.0 flutter bloc, my bloc didn't handle the event in order. |
Starting with Bloc 7.2.0, all events will be processed concurrently so it will be similar to how Cubit works, Read here for how to retain the old behavior |
Hi, I used the _onEvent function with sequential transformer in my target bloc and it works correctly. Thanks for the help. But I still have a lot of if ... else if ... in my _onEvent function just like using the mapEventToState function. |
you can do this class SequentialBloc extends Bloc<SequentialEvent, SequentialState>{
SequentialBloc(): super(SequentialInitial()) {
//We use abstract class as generic so all events that implemented it will be handled by _onEvent
on<SequentialEvent>(_onEvent, transformer : sequential());
}
_onEvent(event, emit){
//Old logic can be put here
}
} This is the pseudocode i wrote without formatting |
Is |
Yup 👍 |
i'm having some trouble with streams...how would I migrate something that looks like this?
where the watchAll() is of type |
A workaround for this change is simply to map the old mapEventToState function in the
And then just remove |
@felangel firstly, absolutely well done on this package. just a quick one, ive been very hard pressed to find a reliable solution to this particular problem domain - connectivity/network connections. My goal here is a simple subscription to give me a yay or nay if my network is not only active, but there is a valid connection available. The event is a singular event returning a boolean value. State is one of Connected or Disconnected. Third party libraries are injected via getIt and Injectable. Id be greatly appreciative of anyones two cents on this snippet. Cheers folks.
|
Hi 👋 Regarding your question, you can do something like: enum ConnectivityState { online, offline, unknown }
class ConnectivityBloc extends Bloc<ConnectivityEvent, ConnectivityState> {
ConnectivityBloc({
required ConnectivityRepository connectivityRepository,
}) : _connectivityRepository = connectivityRepository,
super(ConnectivityState.unknown) {
on<_ConnectivityChanged>((event, emit) => emit(event.connectivityState));
on<ConnectivityRequested>(_onConnectivityRequested);
_connectivityChangedSubscription = _connectivityRepository
.onConnectivityChanged
.listen(_connectivityChanged);
}
final ConnectivityRepository _connectivityRepository;
late StreamSubscription<ConnectivityStatus> _connectivityChangedSubscription;
void _connectivityChanged(ConnectivityStatus connectivityStatus) {
add(
_ConnectivityChanged(
connectivityState: connectivityStatus.toConnectivityState(),
),
);
}
Future<void> _onConnectivityRequested(
ConnectivityRequested event,
Emitter<ConnectivityState> emit,
) async {
try {
final status = await _connectivityRepository.connectivityStatus();
emit(status.toConnectivityState());
} catch (_) {
emit(ConnectivityState.unknown);
}
}
@override
Future<void> close() {
_connectivityChangedSubscription.cancel();
return super.close();
}
}
extension on ConnectivityStatus {
ConnectivityState toConnectivityState() {
switch (this) {
case ConnectivityStatus.offline:
return ConnectivityState.offline;
case ConnectivityStatus.online:
return ConnectivityState.online;
}
}
} Hope that helps 👍 |
I hope external links are alright: @binaryartifex Flutterly has provided an in-depth walkthrough of using the The chapter starts at [1:50:13] and the connectivity part starts at [1:51:40]. https://pub.dev/packages/connectivity_plus I'm looking forward to seeing what I can integrate from Felix's code above with Flutterly's approach. |
Hello, @felangel hope you're doing great! I absolutely love the update and how easy and seamless it is to migrate my blocs! MyBloc() : super(MyBlocInitial()) {
on<MyBlocLoad>(_onMyBlocLoad);
on<MyBlocRedeem>(_onMyBlocRedeem);
} Happy new year and thank you so much for this awesome package! |
Hi, @felangel! I am newbie in Flutter. I use flutter_bloc: 8.0.1 How can I convert to on(..) style this: It is much more complex use case. Here we have to use generator for stream. The project is here: |
Have you seen this? #2526 (comment) Also, I believe this is the recommended approach for working with streams ! bloc/examples/flutter_todos/lib/todos_overview/bloc/todos_overview_bloc.dart Lines 26 to 42 in b6c5514
|
Hi, @felangel! I am newbie in Flutter. I use flutter_bloc: 8.0.1 How can I convert to on(..) style this: class FragmentBloc extends Bloc<FragmentEvent, FragmentState> { |
If static metaprogramming was being made in a way that allow us to specify DSL we improve it even further, like: bloc CounterBloc on int with CounterEvent {
default is 0;
on Increment => emit(state + 1);
on Decrement => emit(state - 1);
} However, it's going to a completely different approach, sadly... |
this is completely awesome and i really like it |
Hello everyone! 👋
First of all, I want to thank everyone for the amazing support and community that has grown around the bloc library! 🙏💙
Context
This proposal aims to address 3 problems with the current
mapEventToState
implementation:Predictability
Due to an issue in Dart, it is not always intuitive what the value of
state
will be when dealing with nested async generators which emit multiple states. Even though there are ways to work around this issue, one of the core principles/goals of the bloc library is to be predictable. Therefore, the primary motivation of this proposal is to make the library as safe as possible to use and eliminate any uncertainty when it comes to the order and value of state changes.Learning Curve and Complexity
Writing blocs requires an understanding of
Streams
and async generators. This means developers must understand how to use theasync*
,yield
, andyield*
keywords. While these concepts are covered in the documentation, they are still fairly complex and difficult for newcomers to grasp.Boilerplate
When writing a bloc, developers must override
mapEventToState
and then handle the incoming event(s). Often times this looks something like:The important logic usually lives inside
_mapEventAToState
and_mapEventBToState
andmapEventToState
ends up mainly being setup code to handle determining which mapper to call based on the event type. It would be nice if this could be streamlined.Proposal 🥁
I am proposing to remove the
mapEventToState
API in favor ofon<Event>
. This would allow developers to register event handlers by callingon<Event>
whereEvent
is the type of event being handled.on<Event>
would provide a callback(Event event, Emitter<State>) {...}
which would be invoked when an event of typeEvent
is added to the bloc. Developers could thenemit
one or more states in response to the incoming event.For example, if we look at the
CounterBloc
for reference, the current implementation might look something like:With the proposed changes the
CounterBloc
would look something like:If we wanted to support multiple events:
For more complex logic it can be refactored to look like:
These changes address the predictability issues mentioned above because it can be guaranteed that the bloc's state will update immediately when
emit
is called ensuring that cases like this behave as expected:In addition, developers don't have to use async generators (
async*
,yield
,yield*
) which can introduce complexity and undesired behavior in advanced cases.This allows developers to focus on the logic by directly registering an event handler for each type of event which streamlines the bloc code a bit further.
An added benefit is the added consistency across
Cubit
andBloc
-- both trigger state changes viaemit
and the transition fromCubit
toBloc
should become simpler.Becomes
Or as mentioned above (for simple cases)
These changes will obviously be breaking changes that impact blocs (cubits will remain unaffected) so they would all be within the scope of a v8.0.0 release.
The changes would be made in a way that only impacts the bloc
mapEventToState
implementation. The way blocs are used and tested will be 100% backward compatible which means the changes will be scoped to just themapEventToState
code and can ideally be automated via a code mod. There should be no impact to the remaining ecosystem (flutter_bloc
,bloc_test
,hydrated_bloc
,replay_bloc
, etc...).Please give this issue a 👍 if you support the proposal or a 👎 if you're against it. If you disagree with the proposal I would really appreciate it if you could comment with your reasoning.
Thanks so much for all of the continued support and looking forward to hearing everyone's thoughts on the proposal! 🙏
08/31 UPDATE
Hey everyone, just wanted to give a quick update:
We currently have the v8.0.0 branch which replaces
mapEventToState
withon<Event>
; however, we were able to makeon<Event>
backward compatible withmapEventToState
🎉 . You can view the changes as part of the v7.2.0 branch.The current plan is to roll out bloc v7.2.0 in the coming days which will deprecate
mapEventToState
,transformEvents
, andtransformTransitions
and will introduce the newon<Event>
API. We will have a comprehensive migration guide explaining all of the changes and how to migrate over. During this time, we encourage everyone to upgrade to bloc v7.2.0 and start to migrate blocs one at a time. In the meantime, we'll be working on development releases of bloc v8.0.0.As part of v8.0.0 all deprecated APIs from v7.2.0 will be removed and the tentative plan is to publish a stable v8.0.0 release about a month after v7.2.0 has been release. This should give everyone some time to incrementally migrate and for any adjustments to be made. In addition, v7.x.x will still receive bug fixes for the foreseeable future so there should be no pressure/urgency to jump to v8.0.0.
Let us know if you have any questions/concerns.
Thanks for everyone's feedback, patience, and continued support! 💙 🙏
09/02 UPDATE
We just published bloc v7.2.0-dev.1 which introduces the
on<Event>
API and is backwards compatible which should allow you to migrate incrementally. 🎉 ✨Release Notes: https://github.com/felangel/bloc/releases/tag/bloc-v7.2.0-dev.1
Please try it out and let us know if you have any feedback/questions, thanks! 🙏 💙
09/09 UPDATE
We just published bloc v7.2.0-dev.2 🎉
Release Notes: https://github.com/felangel/bloc/releases/tag/bloc-v7.2.0-dev.2
09/10 UPDATE
We just published bloc v7.2.0-dev.3 🎉
Release Notes: https://github.com/felangel/bloc/releases/tag/bloc-v7.2.0-dev.3
09/21 UPDATE
The time has come 🥁 🥁 🥁
bloc v7.2.0 is now out 🎉
📦 update now: https://pub.dev/packages/bloc/versions/7.2.0
📔 migration guide: https://bloclibrary.dev/#/migration?id=v720
The text was updated successfully, but these errors were encountered: