Yield several states in a row but not all are received by the BlocBuilder - flutter

I have a BLoC in my app. A View is build upon this BLoC. The View has two main states: let's call them IsGreen and IsRed. Everytime the user taps on a button, the BLoC should switch to another state. So far so fine.
Now I need the the View to display a notification based on a in-between-state. This special state is called IsBlue. If the BLoC switchs from the red state to the green one. During the switch a method is called which can throw an exception or return a result. If the exception is thrown, a notification should be displayed. If the result is valid, show the result.
My mapEventToState looks as follows:
#override
Stream<MyState> mapEventToState(MyEvent event) async* {
switch(event) {
case MyEvent.goRed:
yield IsGreen();
break;
case MyEvent.goGreen:
yield* doSomeStuff();
break;
}
}
Stream<MyState> doSomeStuff() async* {
try {
String result = doSomething();
yield IsBlue(result);
} on MyException {
yield IsException();
}
yield IsGreen();
}
With some logging I found out that the states are properly yielded but the BlocBuilder is not receiving them all. Only the IsGreen and the IsRed events are received. IsBlue and IsException are missed. I don't undestand why. Am I not allowed to send multiple States directly after another?
Does anyone know how often and how fast I can yield state changes?

Related

How to update screen A when navigating back from screen B with bloc flutter

I have a screen(A) which use 'AccountFetched' state to load list of data from database:
return BlocProvider<AccountBloc>(
create: (context) {
return _accountBloc..add(FetchAccountEvent());
},
child: BlocBuilder<AccountBloc, AccountState>(
builder: (context, state) {
if (state is AccountFetched) {
accounts = state.accounts;
}
And in my second screen(B) I call AddAccountEvent to add data to database and navigate back to screen(A) which is the parent screen.
onPressed: () {
BlocProvider.of<AccountBloc>(context)
..add(AddAccountEvent(account: account));
Navigator.of(context).pop();
}
But when I navigate back to screen A, the list of data is not updated.
Should I refresh the screen(A) manually or how can I update the state of bloc?
This is my bloc class:
class AccountBloc extends Bloc<AccountEvent, AccountState> {
AccountBloc() : super(AccountInitial());
#override
Stream<AccountState> mapEventToState(AccountEvent event) async* {
if (event is FetchAccountEvent) yield* _fetchAccountEvent(event, state);
if (event is AddAccountEvent) yield* _addAccountEvent(event, state);
}
Stream<AccountState> _fetchAccountEvent(
FetchAccountEvent event, AccountState state) async* {
yield FetchingAccount();
final dbAccount = await DBAccountRepository.instance;
List<Account> accounts = await dbAccount.accounts();
yield AccountFetched(accounts: accounts);
}
Stream<AccountState> _addAccountEvent(
AddAccountEvent event, AccountState state) async* {
yield AddingAccount();
final dbAccount = await DBAccountRepository.instance;
final insertedId = await dbAccount.insertAccount(event.account);
yield AccountAdded(insertedId: insertedId);
}
}
I was experiencing the same problem, the solution I found was to make screen A wait for screen B to close, and after that trigger the data reload event for screen A.
onTap: () async {
await Navigator.of(context).push(ScreenB());
_bloc.add(RequestDataEvent());
}
So it will only execute the event after screen B is closed
well, you are creating a new bloc on page A. try to move BlocProvider 1 level up. so when the build function of page A calls, doesn't create a new bloc.
You should have a BlocListener in screen A listening for AccountState , with a if ( state is AccountFetched ) {} statement and perform your data update in a setState() . Depending on how long it takes to display screen A you might have the AccountFetched state been yielded before so screen A won't see it, in this case you could delay by 250 milliseconds yielding it in your _addAccount method either with a Timer or a Future.delayed.
Hope it helped.

Dart/Flutter Yield state after timer

I am using flutter_bloc and I am not sure how to yield state from within a callback.
Am trying to start timer and return a new state after a pause. Reason I am using a timer is to get the ability to cancel previous timer so it always returns a new state after an idle state.
#override
Stream<VerseState> mapEventToState(
VerseEvent event,
) async* {
if (event is ABCEvent) {
Timer(const Duration(seconds: 3), () {
print("Here");
_onTimerEnd(); // adding yield here returns an error.
})
}
Stream<XYZState> _onTimerEnd() async* {
print("Yielding a state");
yield myNewState();
}
I can see that the code is getting inside the timer callback as I can see the print statements in the callback but not in the timerEnd() method.
State should be yielded by the stream used in bloc that you are current working on. Like
mapEventToState

BlocBuilder builder function gets called only once

Good evening, I have been working with bloc pattern and I have some issues: The state update is being called only once on BlocBuilder, no matter what I do
What I have as state is:
class DateScreenState {
Future<List<PrimaryPetModifierModel>> primaryPetModifiers;
Future<List<SecondaryPetModifierModel>> secondaryPetModifiers;
PrimaryPetModifierModel primaryPetModifierSelected;
SecondaryPetModifierModel secondaryPetModifierSelected;
Widget animatedWidget;
DateTime dateOfSchedule;
DateTime timeOfSchedule;
bool shouldReload = false;
bool isFirstCallAnimation = true;
}
And my mapEventToState looks like this:
#override
Stream<DateScreenState> mapEventToState(
ScheduleDateScreenEvent event) async* {
if (event is SelectPrimaryModifierEvent) {
state.primaryPetModifierSelected = event.modifier;
yield state;
} else if (event is SelectSecondaryModifierEvent) {
state.secondaryPetModifierSelected = event.modifier;
yield state;
}
}
My exact issue is, when I change a value in a DropdownButton, it will fire a SelectedPrimaryModifierEvent or SelectedSecondaryModifier event, the event firing works fine, but the state yielding and updating will happen only once after the first fire of any of those events, after that, BlocBuilder builder function will not be called anymore after any event.
You're yielding the same state with each event, despite the fact that you're changing a variable with the DateScreenState class. Try splitting up your primary and secondary into different state classes and yield them separately in your mapEventToState.

Flutter bloc adding 2 event in same time

I wanna check users internet connection and firebase auth state changes in my app. I am using flutter bloc for my app's state management. But when call different 2 .add(event) in one initstate always the first one is run and changes states but second one didnt run didnt change state. What is the my wrong ?
my bloc:
class ControllerBloc extends Bloc<ControllerEvent, ControllerState> {
ControllerBloc() : super(ControllerInitial());
AuthApiClient _authApiClient = getIt<AuthApiClient>();
#override
Stream<ControllerState> mapEventToState(
ControllerEvent event,
) async* {
if (event is ControllInternetConnection) {
yield* internetControll();
}
if (event is ControllUserAuth) {
debugPrint("wwwwgeldi");
yield* userAuthControl();
}
// TODO: implement mapEventToState
}
Stream<ControllerState> internetControll() async* {
Stream<DataConnectionStatus> connectionState =
DataConnectionChecker().onStatusChange;
await for (DataConnectionStatus status in connectionState) {
switch (status) {
case DataConnectionStatus.connected:
debugPrint("Bağlandı");
yield InternetConnectedState();
break;
case DataConnectionStatus.disconnected:
debugPrint("Kesildi");
yield InternetConnectionLostState();
break;
}
}
}
Stream<ControllerState> userAuthControl() async* {
FirebaseAuth firebaseAuth = _authApiClient.authInstanceAl();
debugPrint("geldi");
Stream<User> authStream = firebaseAuth.authStateChanges();
_authApiClient.authInstanceAl().signOut();
await for (User authUserResult in authStream) {
if (authUserResult == null) {
yield UserAuthControlError();
}
}
}
}
my page where call my events
class _NavigationPageState extends State<NavigationPage> {
ControllerBloc controllerBloc;
#override
void initState() {
controllerBloc= BlocProvider.of<ControllerBloc>(context);
controllerBloc.add(ControllInternetConnection());
controllerBloc.add(ControllUserAuth());
super.initState();
}
If I am understanding this right, it looks to me that you are trying to solve two different problems with one BLoC. I don't see a reason why internet connection and user auth have to be in one BLoC, rather I would just separate the two in separate BLoCs.
As the discussion in this thread points out, the point of using a BLoC revolves around the idea of predictability purposes. You can override the existing BLoC event stream, but I personally think that is too complicated for what you are trying to do.
So I would suggest, either make two separate BLoCs or combine the entire process into one event, where the internet connection would be checked before the user is authenticated, you will then return different states depending on the errors.

Awaiting some results before dispatching an event with flutter_bloc library

I am trying to create a BLOC which depends on two other time based bloc and a non-time based bloc. What i mean with time based is, for example they are connecting a remote server so it takes time. It's working just like this:
Login (It's of course taking some time)
If login is successful
Do another process (This is something takes time also. It returns a future.)
After login and another process finishes, let the page know it.
My BLOC depends on these three:
final UserBloc _userBloc;
final AnotherBloc _anotherBloc;
final FinishBloc _finishBloc;
Inside the map event to state method I should dispatch relevant events. However i cannot await if they are finished.
_userBloc.dispatch(
Login(),
);
_anotherBloc.dispatch(
AnotherProcess(),
);
//LetThePageKnowIt should work after login and another process
_finishBloc.dispatch(
LetThePageKnowIt(),
);
Is there a clean way to await some others before dispatching something?
Right know I use a way that i don't like. In the main bloc's state which i connect other blocs in it, I have bools.
class CombinerState {
bool isLoginFinished = false;
bool isAnotherProcessFinished = false;
I am listening the time dependent blocs' states in constructor of main bloc. When they yield "i am finished" I just mark the bools "true".
MainBloc(
this._userBloc,
this._anotherBloc,
this._pageBloc,
); {
_userBloc.state.listen(
(state) {
if (state.status == Status.finished) {
dispatch(FinishLogin());
}
},
);
_anotherBloc.state.listen(
(state) {
if (state.status == AnotherStatus.finished) {
dispatch(FinishAnotherProcess());
}
},
);
}
and I dispatch another event for main bloc to check if all the bools are true after setting a bool to true.
else if (event is FinishAnotherProcess) {
newState.isAnotherProcessFinished = true;
yield newState;
dispatch(CheckIfReady());
}
If the bools are true, i dispatch LetThePageKnowIt()
else if (event is CheckIfReady) {
if (currentState.isAnotherProcessFinished == true &&
currentState.isLoginFinished == true) {
_pageBloc.dispatch(LetThePageKnowIt());
}
}
I am not satisfied with this code. I am looking a way to await other BLOCs send a state with "finished". After that I want to dispatch my LetThePageKnowIt()
#pskink 's suggestion solved my problem.
I have created two methods which return a future. Inside of them, I just await for my streams. Here is the example of login stream.
In map event to state, after the dispatches, I await an async method.
_userBloc.dispatch(
Login(),
);
_anotherBloc.dispatch(
AnotherProcess(),
);
await loginProcess();
await otherProcess();
_finishBloc.dispatch(
LetThePageKnowIt(),
);
Inside the method I just await for userbloc to finish its job and yields about it. Then return.
Future loginProcess() async {
await for (var result in _userBloc.state) {
if (result.status == Status.finished) {
return;
}
}
}