Closed felangel closed 3 years ago
Hey, I've been reading about this and am very excited for the changes and the simplicity that comes with it. I was brought here while having problems with nested async generators and I just couldn't find the solution for it so I wanted to try out the new changes.
But I would like some help regarding this. I copy pasted the overrides and ran pub get and everything went smoothly. I updated one of my blocs to use the new on<>
and it was all fine until something went wrong (I'm not sure what) and I had to run pub get again. This was the error I received
Git error. Command: `git fetch`
stdout:
stderr: error: cannot lock ref 'refs/heads/feat/replace-mapEventToState-with-on<E>': Unable to create 'C:/Users/unoff/AppData/Roaming/Pub/Cache/git/cache/bloc-f84666e53401b96b922e28d5fd764038224e0c04/./refs/heads/feat/replace-mapEventToState-with-on<E>.lock': Invalid argument
From https://github.com/felangel/bloc
! 7a679116..aad16060 feat/replace-mapEventToState-with-on<E> -> feat/replace-mapEventToState-with-on<E> (unable to update local ref)
exit code: 1
pub get failed (server unavailable) -- attempting retry 2 in 2 seconds...
I've tried running flutter clean
, flutter pub cache repair
and it still wouldn't work.
Any help is appreciated!
Also, when I was updating my bloc, none of my other blocs have errors, while they were using mapEventToState
, was that supposed to happen? :- Though the the blocs didnt have syntax errors, the actual bloc does not work on runtime.
Update : I managed to fix this by reinstalling flutter as a whole. The problem probably rose from me not removing fvm correctly and my flutter directory was more or less a mess. Cleaning this up fixed it :)
Here's me using the bloc with no async generators and working perfectly fine. I love it so much I could marry it. Before, when I wanted to run some methods on my CabinetCubit
I would have to use async*
to yield states, and in my case it just wouldn't work, now it does. Thanks felix. (✿◠‿◠).
list_screen_bloc.dart
class ListScreenBloc extends Bloc<ListScreenEvent, ListScreenState> {
final CabinetCubit cabinetCubit;
final AuthCubit authCubit;
ListScreenBloc({
required this.authCubit,
required this.cabinetCubit,
}) : super(const LSInitial()) {
on<_Started>(start);
on<LSexecGetAllListSGSnip>(execGetAllListSGSnip);
}
FutureOr<void> start(
ListScreenEvent event,
Emitter<ListScreenState> emit,
) {
emit(state);
}
FutureOr<void> execGetAllListSGSnip(
ListScreenEvent event,
Emitter<ListScreenState> emit,
) {
print('execGetAllListSGSnip started');
emit(const ListScreenState.loading(
currentOperationMessage: 'Retrieving your groups..'));
String uid;
authCubit.execGetUserUid().then((_) async {
print('execGetUserUid completed');
AuthState authState = authCubit.state;
if (authState is AuthGeneralCompleted) {
print('UID Retrieved');
uid = authState.content;
await cabinetCubit.execGetAllList(uid: uid).then(
(_) async {
CabinetState cabinetState = cabinetCubit.state;
if (cabinetState is CabinetLoadedGetAllListSGSnip) {
emit(ListScreenState.loadedAllListSGSnip(
cabinetState.allListSGSnip));
} else if (cabinetState is CabinetErrorGetAllListSGSnip) {
emit(ListScreenState.error(
message: cabinetState.message,
code: cabinetState.code,
));
} else {
print('cabinetCubit unexpected state ; $cabinetState');
throw UnexpectedException(
code: 'cabinet_cubit_exception',
message: 'what the hej happened',
);
}
print('execGetAllList completed');
},
);
} else if (authState is AuthError) {
print('AuthCubit Error');
emit(
ListScreenState.error(
message: authState.message,
code: authState.code ?? 'No code was provided'),
);
} else {
print('authCubit unexpected state ; $authState');
throw UnexpectedException(code: 'code', message: 'authCubit');
}
});
}
}
was that supposed to happen?
yeah, that's it. Besides, when you send any event that has no listener (on
@datdefboi just updated replay_bloc
in 1baa6820629187bbb23a7d799b8c7e9fe73f9a85
let me know if you run into any issues and thanks for your patience!
I think it would be awesome to have an intermediate release that would let all the people know that the old API is going to be deprecated. I think only a limited number of people are aware of the proposal and having a deprecation notice in the code might be a really good way to gather more feedback.
Hey everyone, just wanted to give a quick update:
We currently have the v8.0.0 branch which replaces mapEventToState
with on<Event>
; however, we were able to make on<Event>
backward compatible with mapEventToState
🎉 . 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
, and transformTransitions
and will introduce the new on<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! 💙 🙏
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! 🙏 💙
Just published bloc v7.2.0-dev.2 🎉 Release Notes: https://github.com/felangel/bloc/releases/tag/bloc-v7.2.0-dev.2
Just published bloc v7.2.0-dev.3 🎉 Release Notes: https://github.com/felangel/bloc/releases/tag/bloc-v7.2.0-dev.3
Following this comment I'm wondering if it would be worth encouraging a different name convention for the emit
argument used in on()
to avoid confusion with the internal emit
used by Bloc
/ BlocBase
.
If you're migrating from mapEventToState
it seems like an easy mistake to make that you forget to pass in the emit
provided by on
and end up using the internal emit
. For example, let's imagine we migrated this bloc from a combination of bloc and freezed (using map
):
// Renamed from `mapEventToState`
class MyBloc {
MyBloc() {
on<MyEvent>(myEventHandler);
}
// renamed/migrated from mapEventToState
myEventHandler(event, emit) {
event.map(
eventA: (event, emit) => eventAHandler(event) // whoops, we didn't pass in `emit`
)
}
eventAHandler(event) async {
emit(newState); // this "works" but it's using the internal bloc emit
}
}
It's a bit of an edge-case but it does seem open to confusion which could be avoided by just encouraging people to use emitState
,setState
or some other name.
Another option might be to remove @protected
from the internal emit
and just leave it as @visibleForTesting
. It seems that because it annotated with both @protected
and @visibleForTesting
that the editor does not complain about using it (in VSCode at least):
For example, this is using the internal emit:
However, if you remove @protected
and leave the @visibleForTesting
you get the warning as expected:
The same line after manually removing @protected
from it:
Following this comment I'm wondering if it would be worth encouraging a different name convention for the
emit
argument used inon()
to avoid confusion with the internalemit
used byBloc
/BlocBase
.If you're migrating from
mapEventToState
it seems like an easy mistake to make that you forget to pass in theemit
provided byon
and end up using the internalemit
. For example, let's imagine we migrated this bloc from a combination of bloc and freezed (usingmap
):// Renamed from `mapEventToState` class MyBloc { MyBloc() { on<MyEvent>(myEventHandler); } // renamed/migrated from mapEventToState myEventHandler(event, emit) { event.map( eventA: (event, emit) => eventAHandler(event) // whoops, we didn't pass in `emit` ) } eventAHandler(event) async { emit(newState); // this "works" but it's using the internal bloc emit } }
It's a bit of an edge-case but it does seem open to confusion which could be avoided by just encouraging people to use
emitState
,setState
or some other name.Another option might be to remove
@protected
from the internalemit
and just leave it as@visibleForTesting
. It seems that because it annotated with both@protected
and@visibleForTesting
that the editor does not complain about using it (in VSCode at least):For example, this is using the internal emit:
However, if you remove
@protected
and leave the@visibleForTesting
you get the warning as expected:The same line after manually removing
@protected
from it:
Good catch! I removed the protected
decorator 👍
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
@felangel Hi!
Now I start to update to the new flutter_bloc version.
I use freezed package, with the previous version, I can do this:
@override
Stream<MyState> mapEventToState(MyEvent event,) =>
state.map(
init: (_) =>
event.maybeMap(
load: (_) => _load(),
orElse: () => Stream.empty(),
),
data: (_) =>
event.maybeMap(
reload: (_) => _reload(),
orElse: () => Stream.empty(),
),
);
With the previous version, I can use bloc like FSM without a boilerplate. But now I should check state inside on<Event>
handler. Can you help me, how I can migrate better? How I can check state as easily as check event?
@felangel Hi!
Now I start to update to the new flutter_bloc version.
I use freezed package, with the previous version, I can do this:
@override Stream<MyState> mapEventToState(MyEvent event,) => state.map( init: (_) => event.maybeMap( load: (_) => _load(), orElse: () => Stream.empty(), ), data: (_) => event.maybeMap( reload: (_) => _reload(), orElse: () => Stream.empty(), ), );
With the previous version, I can use bloc like FSM without a boilerplate. But now I should check state inside
on<Event>
handler. Can you help me, how I can migrate better? How I can check state as easily as check event?
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:
By default, events will be processed concurrently when using on<E> as opposed to mapEventToState which processes events sequentially.
Example:
on<Event1>( asyncHandler1 ); // emits state(1), awaits some async works, emits state(2)
on<Event2>( syncHandler2 ); // emits state(3) without any 'await's
Does it mean that if bloc receives [ Event1(), Event2() ]
the emitted states will be [ state(1), state(3), state(2) ]
?
Is the fact that events are processed concurrently also valid for a single handler ? In previous example, if user feeds the bloc [ Event1(1), Event1(2) ]
and handling of Event1(1)
takes much longer time then Event1(2)
, will the output states from the bloc will be state(s) emited from handling of Event1(2)
first, and then state(s) emitted from handling of Event1(1)
?
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!
Does it mean that if bloc receives [ Event1(), Event2() ] the emitted states will be [ state(1), state(3), state(2) ] ?
In that example, the emitted states will be: [state(1), state(3), state(2)] 👍
Is the fact that events are processed concurrently also valid for a single handler ?
Yup by default each event handler processes events concurrently and multiple registered handlers function independently.
In previous example, if user feeds the bloc [ Event1(1), Event1(2) ] and handling of Event1(1) takes much longer time then Event1(2), will the output states from the bloc will be state(s) emitted from handling of Event1(2) first, and then state(s) emitted from handling of Event1(1) ?
Yes, that's correct. If that's undesirable you can apply a transformer like droppable
(to ignore the second event if the initial event is still processing) or restartable
if you want to cancel the pending event and immediately process the new one 👍
"By default, events will be processed concurrently" - so is there a way to actually force a bloc to handle events sequentially ?
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 sequential
transformer like:
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 EventTransformer
for all blocs in your application:
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?
@override
Stream<AuthState> mapEventToState( AuthEvent event,) async* {
yield* event.map(
authCheckRequested: (e) async*{
final userOption = await _authFacade.getSignedInUserId();
yield userOption.fold(
() => const AuthState.unauthenticated(),
(_) => const AuthState.authenticated(),
);
},
signedOut: (e) async*{
await _authFacade.signOut();
yield const AuthState.unauthenticated();
}
);
}
}
how can I migrate this?
@override Stream<AuthState> mapEventToState( AuthEvent event,) async* { yield* event.map( authCheckRequested: (e) async*{ final userOption = await _authFacade.getSignedInUserId(); yield userOption.fold( () => const AuthState.unauthenticated(), (_) => const AuthState.authenticated(), ); }, signedOut: (e) async*{ await _authFacade.signOut(); yield const AuthState.unauthenticated(); } ); } }
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. How can I fix this? Thanks.
Hi, after migrating to v7.2.0 flutter bloc, my bloc didn't handle the event in order. How can I fix this? Thanks.
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, after migrating to v7.2.0 flutter bloc, my bloc didn't handle the event in order. How can I fix this? Thanks.
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.
Using Bloc.transformer = sequential
But I still have a lot of if ... else if ... in my _onEvent function just like using the mapEventToState function. So I have to make all blocs using _onEvent function or is there anything else I can do? Thanks.
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
@felangel It is very clean, I would like to see a similar way to the Freezed library
state.When/MaybeWhen
andstate.Map/MaybeMap
and of coursestate.copyWith
tooThanks! You should still be able to use
freezed
withwhen
,copyWith
, etc... with this approachclass 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(...)), ... } }
void _onEvent(MyEvent event, Emit<MyState> emit) async {...}
Is Emit
now renamed to Emitter
?
@felangel It is very clean, I would like to see a similar way to the Freezed library
state.When/MaybeWhen
andstate.Map/MaybeMap
and of coursestate.copyWith
tooThanks! You should still be able to use
freezed
withwhen
,copyWith
, etc... with this approachclass 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(...)), ... } }
void _onEvent(MyEvent event, Emit<MyState> emit) async {...}
Is
Emit
now renamed toEmitter
?
Yup 👍 You can check out the migration guide for more info at https://bloclibrary.dev/#/migration
i'm having some trouble with streams...how would I migrate something that looks like this?
class QuestionWatcherBloc extends Bloc<QuestionWatcherEvent, QuestionWatcherState> {
final IQuestionRepository _questionRepository;
QuestionWatcherBloc(
this._questionRepository,
) : super(const QuestionWatcherState.initial());
@override
Stream<QuestionWatcherState> mapEventToState(
QuestionWatcherEvent event,
) async* {
yield const QuestionWatcherState.loadInProgress();
yield* _questionRepository
.watchAll()
.map((failureOrQuestions) => failureOrQuestions.fold(
(f) => QuestionWatcherState.loadFailure(f),
(questions) => QuestionWatcherState.loadSuccess(questions),
));
}
}
where the watchAll() is of type Stream<Either<QuestionFailure, KtList<Question>>>
and listens to any changes in firebase?
A workaround for this change is simply to map the old mapEventToState function in the on
handler, like this:
on<EventType>((event, emit) async {
await emit.onEach(mapEventToState(event), onData: (StateType state) {
emit(state);
});
}, transformer: sequential());
And then just remove @override
annotation from mapEventToState
. Note, that sequential()
transformer comes from bloc_concurrency
package.
@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.
@injectable
class ConnectivityBloc extends Bloc<ConnectivityEvent, ConnectivityState> {
final Connectivity connectivity;
final InternetConnectionChecker connectionChecker;
StreamSubscription<ConnectivityResult>? _streamSubscription;
ConnectivityBloc({
required this.connectivity,
required this.connectionChecker,
}) : super(const Disconnected()) {
on<ConnectionChanged>(_onConnectionChanged);
_streamSubscription = connectivity.onConnectivityChanged.listen((status) async {
if (status != ConnectivityResult.none) {
final hasConnection = await connectionChecker.hasConnection;
add(ConnectionChanged(hasConnection: hasConnection));
}
});
}
@override
Future<void> close() {
_streamSubscription?.cancel();
return super.close();
}
void _onConnectionChanged(ConnectionChanged event, Emitter<ConnectivityState> emit) {
final state = event.hasConnection ? const Connected() : const Disconnected();
emit(state);
}
}
@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.
@injectable class ConnectivityBloc extends Bloc<ConnectivityEvent, ConnectivityState> { final Connectivity connectivity; final InternetConnectionChecker connectionChecker; StreamSubscription<ConnectivityResult>? _streamSubscription; ConnectivityBloc({ required this.connectivity, required this.connectionChecker, }) : super(const Disconnected()) { on<ConnectionChanged>(_onConnectionChanged); _streamSubscription = connectivity.onConnectivityChanged.listen((status) async { if (status != ConnectivityResult.none) { final hasConnection = await connectionChecker.hasConnection; add(ConnectionChanged(hasConnection: hasConnection)); } }); } @override Future<void> close() { _streamSubscription?.cancel(); return super.close(); } void _onConnectionChanged(ConnectionChanged event, Emitter<ConnectivityState> emit) { final state = event.hasConnection ? const Connected() : const Disconnected(); emit(state); } }
Hi 👋 Thanks for the positive feedback, I really appreciate it! 🙏 💙
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 connectivity_plus
package using blocs as part of his full-length Flutter Bloc presentation tutorial.
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! I just have a quick question. How would I wrap all of my events with a single try and catch since whatever I'm doing on catch is the same for both events?
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@override
Stream<RemoteArticlesState> mapEventToState(RemoteArticlesEvent event) async* {
if (event is GetArticles) yield* _getBreakingNewsArticle(event);
}
It is much more complex use case. Here we have to use generator for stream.
The project is here:
How can I convert to on(..) style this:
Have you seen this? https://github.com/felangel/bloc/issues/2526#issuecomment-1046214602
Also, I believe this is the recommended approach for working with streams ! emit.forEach
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> {
@override
FragmentState get initialState => FragmentInitialState();
@override
Stream
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...
How would I migrate this to the new version?
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